web-dev-qa-db-ja.com

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

コンテキストマネージャに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
31
Peter

関数がどのタイプを受け入れるか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]コメントでも示唆されているように、うまくいきますか?

これは、GeneratorIteratorのサブタイプであるためです-私たちは再びこれを自分で確認できます typeshedを参照することにより 。したがって、関数がジェネレータを返す場合でも、それはcontextmanagerが期待するものと互換性があるため、mypyは問題なくそれを受け入れます。

8
Michael0x2a

_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]:
    ...
_
4
Joe

コンテキストマネージャによってラップされた関数の戻りの型はIterator[None]

from contextlib import contextmanager
from typing import Iterator

@contextmanager
def foo() -> Iterator[None]:
    yield
1
David Foster