1. ホーム
  2. python

Python のタイプヒントとコンテキストマネージャ

2023-09-10 17:47:45

質問

コンテキストマネージャはどのようにPythonの型ヒントをアノテーションするべきですか?

import typing

@contextlib.contextmanager
def foo() -> ???:
    yield

この のドキュメントは、contextlib の のドキュメントでは、あまり型について触れていません。

のドキュメントは typing.ContextManager について述べています。 のドキュメントもそれほど役に立つものではありません。

また typing.Generator があり、少なくとも例がある。ということは、私は typing.Generator[None, None, None] を使うべきで typing.ContextManager ?

import typing

@contextlib.contextmanager
def foo() -> typing.Generator[None, None, None]:
    yield

どのように解決するのですか?

ある関数がどのような型を受け付けるのか100%わからないときは、私はいつも typehed これはPythonのための型ヒントの標準的なリポジトリです。Mypyは型チェックを行うためにtypeshedを直接バンドルし、使用しています。

contextlibのスタブはここで見つけることができます。 https://github.com/python/typeshed/blob/master/stdlib/contextlib.pyi

if sys.version_info >= (3, 2):
    class GeneratorContextManager(ContextManager[_T], Generic[_T]):
        def __call__(self, func: Callable[..., _T]) -> Callable[..., _T]: ...
    def contextmanager(func: Callable[..., Iterator[_T]]) -> Callable[..., GeneratorContextManager[_T]]: ...
else:
    def contextmanager(func: Callable[..., Iterator[_T]]) -> Callable[..., ContextManager[_T]]: ...

ちょっと無理がありますが、気になるのはこのラインです。

def contextmanager(func: Callable[..., Iterator[_T]]) -> Callable[..., ContextManager[_T]]: ...

デコレータが取り込むのは Callable[..., Iterator[_T]] -- 何らかのイテレータを返す、任意の引数を持つ関数です。というわけで、結論から言うと、やっても問題ないでしょう。

@contextlib.contextmanager
def foo() -> Iterator[None]:
    yield

では、なぜ Generator[None, None, None] を使っても、コメントで提案されているように動作するのはなぜでしょうか?

それは Generator のサブタイプだからです。 Iterator -- のサブタイプです。 は、typeshedを参照することで . ですから、もしこの関数がジェネレータを返したとしても、それは contextmanager が期待するものと互換性があるので、mypy は問題なくそれを受け入れます。