如何使用Python类型提示注释上下文管理器?
import typing
@contextlib.contextmanager
def foo() -> ???:
yield
Run Code Online (Sandbox Code Playgroud)
关于contextlib的文档并未提及类型.
关于typing.ContextManager的文档也不是那么有用.
还有typing.Generator,至少有一个例子.这是否意味着我应该使用typing.Generator[None, None, None]而不是typing.ContextManager?
import typing
@contextlib.contextmanager
def foo() -> typing.Generator[None, None, None]:
yield
Run Code Online (Sandbox Code Playgroud)
小智 31
我在这里没有找到关于注释上下文管理器的好答案,这些上下文管理器以通过mypyPython 3.10 下的检查的方式产生值。根据contextlib.contextmanager 的 Python 3.10 文档
\n\n被修饰的函数在调用时必须返回 a\xc2\xa0生成器迭代器
\n
Typing.Generators注释为Generator[YieldType,\xc2\xa0SendType,\xc2\xa0ReturnType]。因此,对于产生 a 的函数pathlib.Path,我们可以这样注释我们的函数:
from typing import Generator\nfrom contextlib import contextmanager\n\n@contextmanager\ndef working_directory() -> Generator[Path, None, None]:\n with TemporaryDirectory() as td:\n yield Path(td)\nRun Code Online (Sandbox Code Playgroud)\n但是,Generators它没有指定SendType或ReturnType可以注释为typing.Iterator:
from typing import Iterator\nfrom contextlib import contextmanager\n\n@contextmanager\ndef working_directory() -> Iterator[Path]:\n with TemporaryDirectory() as td:\n yield Path(td)\nRun Code Online (Sandbox Code Playgroud)\n最后,由于PEP 585——标准集合中的类型提示泛型在 Python 3.9 中被采用,typing.Iterator并且typing.Generator已被弃用,以支持collections.abc实现
from collections.abc import Iterator\nfrom contextlib import contextmanager\n\n@contextmanager\ndef working_directory() -> Iterator[Path]:\n with TemporaryDirectory() as td:\n yield Path(td)\nRun Code Online (Sandbox Code Playgroud)\n
kol*_*pto 27
使用 PyCharm,我执行以下操作以使其类型提示起作用:
from contextlib import contextmanager
from typing import ContextManager
@contextmanager
def session() -> ContextManager[Session]:
yield Session(...)
Run Code Online (Sandbox Code Playgroud)
Dav*_*ter 15
A. 修饰的函数的返回类型@contextmanager是Iterator[None]。
from contextlib import contextmanager
from typing import Iterator
@contextmanager
def foo() -> Iterator[None]:
yield
Run Code Online (Sandbox Code Playgroud)
B. 上下文管理器本身的类型是AbstractContextManager:
from contextlib import AbstractContextManager
def make_it_so(context: AbstractContextManager) -> None:
with context:
...
Run Code Online (Sandbox Code Playgroud)
您可能还会看到typing.ContextManagerused,但从Python 3.9 开始,它已被弃用。contextlib.AbstractContextManager
Mic*_*x2a 10
每当我不确定100%是否接受函数的类型时,我都喜欢咨询typeshed,这是Python类型提示的规范存储库。例如,Mypy直接捆绑并使用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]]: ...
Run Code Online (Sandbox Code Playgroud)
这有点让人不知所措,但是我们关心的是这一行:
def contextmanager(func: Callable[..., Iterator[_T]]) -> Callable[..., ContextManager[_T]]: ...
Run Code Online (Sandbox Code Playgroud)
它声明装饰器接受一个Callable[..., Iterator[_T]]-具有任意参数的函数,该函数返回一些迭代器。因此,总而言之,这样做很好:
@contextlib.contextmanager
def foo() -> Iterator[None]:
yield
Run Code Online (Sandbox Code Playgroud)
那么,为什么Generator[None, None, None]如注释所建议的那样使用也能起作用?
这是因为Generator是的子类型Iterator-我们可以通过咨询typeshed重新检查一下。因此,如果我们的函数返回一个生成器,它仍然与contextmanager期望的兼容,因此mypy可以毫无问题地接受它。
Iterator[]当您想要返回上下文管理器的引用时,该版本不起作用。例如,以下代码:
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]:
...
Run Code Online (Sandbox Code Playgroud)
会产生一个错误就return assert_timing(high=seconds)行:
Incompatible return value type (got "_GeneratorContextManager[None]", expected "Iterator[None]")
该函数的任何合法用法:
with assert_faster_than(1):
be_quick()
Run Code Online (Sandbox Code Playgroud)
将导致这样的事情:
"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__"?
Run Code Online (Sandbox Code Playgroud)
你可以像这样修复它......
def assert_faster_than(...) -> Iterator[None]:
with assert_timing(...):
yield
Run Code Online (Sandbox Code Playgroud)
但是我将使用新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]:
...
Run Code Online (Sandbox Code Playgroud)
基于PEP-585,正确的注释类型似乎是AbstractContextManager(参见https://www.python.org/dev/peps/pep-0585/#implementation)。您可以使用以下代码:
import contextlib
@contextlib.contextmanager
def foo() -> contextlib.AbstractContextManager[None]:
yield
Run Code Online (Sandbox Code Playgroud)
这是与 PyCharm 一起正确工作的唯一解决方案(以及typing.ContextManager,但该解决方案应从 Python 3.9 中弃用)。当您在with语句(类型提示)中使用它时,它会正确地帮助您,这非常有帮助。
但是当我回到最初的问题(“应该如何用 Python 类型提示注释上下文管理器?”)时,这取决于。从我的角度来看,正确的应该是我提到的那个。但这似乎不适用于 mypy(还)。有一些关于此 PEP 的更新(参见https://github.com/python/mypy/issues/7907),但由于我对 mypy 的经验不多,我可能在这里遗漏了一些东西。