コンテキストマネージャに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%わからないときはいつでも、Pythonのタイプヒントの正規リポジトリである typeshed を調べたいと思います。 Mypyは直接タイプバンドルし、タイプチェックを使用してタイプチェックなどを実行します。
ここでcontextlibのスタブを見つけることができます: https://github.com/python/typeshed/blob/master/stdlib/2and3/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は問題なくそれを受け入れます。
_Iterator[]
_バージョンは、contextmanagerの参照を返すときに機能しません。たとえば、次のコード:
_from typing import Iterator
def assert_faster_than(seconds: float) -> Iterator[None]:
return assert_timing(high=seconds)
@contextmanager
def assert_timing(low: float = 0, high: float = None) -> Iterator[None]:
...
_
return assert_timing(high=seconds)
行でエラーが発生します:
Incompatible return value type (got "_GeneratorContextManager[None]", expected "Iterator[None]")
関数の正当な使用法:
_with assert_faster_than(1):
be_quick()
_
このような結果になります:
_"Iterator[None]" has no attribute "__enter__"; maybe "__iter__"?
"Iterator[None]" has no attribute "__exit__"; maybe "__next__"?
"Iterator[None]" has no attribute "__enter__"; maybe "__iter__"?
"Iterator[None]" has no attribute "__exit__"; maybe "__next__"?
_
このように修正できます...
_def assert_faster_than(...) -> Iterator[None]:
with assert_timing(...):
yield
_
しかし、代わりに新しい_ContextManager[]
_オブジェクトを使用して、デコレータのmypyを無音にします。
_from typing import ContextManager
def assert_faster_than(seconds: float) -> ContextManager[None]:
return assert_timing(high=seconds)
@contextmanager # type: ignore
def assert_timing(low: float = 0, high: float = None) -> ContextManager[None]:
...
_
コンテキストマネージャによってラップされた関数の戻りの型はIterator[None]
。
from contextlib import contextmanager
from typing import Iterator
@contextmanager
def foo() -> Iterator[None]:
yield