上下文管理器应如何使用Python类型提示进行注释?

import typing

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

documentation on contextlib没有太多提及类型。

documentation on typing.ContextManager也不是那么有用。

还有typing.Generator,至少有一个例子。这是否意味着我应该使用typing.Generator[None, None, None]而不是typing.ContextManager
import typing

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

最佳答案

每当我不确定100%是否接受函数的类型时,我都喜欢咨询typeshed,它是Python类型提示的规范存储库。例如,Mypy直接 bundle 并使用typeshed帮助其执行类型检查。

我们可以在此处找到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的子类型-我们可以再次为自己检查by consulting typeshed。因此,如果我们的函数返回一个生成器,它仍然与contextmanager期望的兼容,因此mypy可以毫无问题地接受它。

10-01 03:39
查看更多