每当我不确定一个函数接受哪些类型时,我会查阅 typeshed,这是 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 会毫无问题地接受它。
None
,因此它是一个Generator[None, None, None]
。无论您是否将其用于上下文管理器都没有关系。 - internet_userNone
。 - Peter