Python类型提示和上下文管理器

Pet*_*ter 19 python mypy

如何使用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
\n

Typing.Generators注释为Generator[YieldType,\xc2\xa0SendType,\xc2\xa0ReturnType]。因此,对于产生 a 的函数pathlib.Path,我们可以这样注释我们的函数:

\n
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)\n
Run Code Online (Sandbox Code Playgroud)\n

但是,Generators它没有指定SendTypeReturnType可以注释为typing.Iterator

\n
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)\n
Run Code Online (Sandbox Code Playgroud)\n

最后,由于PEP 585——标准集合中的类型提示泛型在 Python 3.9 中被采用,typing.Iterator并且typing.Generator已被弃用,以支持collections.abc实现

\n
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)\n
Run Code Online (Sandbox Code Playgroud)\n

  • **谢谢,这是一个很棒的答案。** 我可以确认 `Generator[T, None, None]` 是正确的类型注释。MyPy 对此非常满意。(哦,关于注释为 `ContextManager[T]` 的其他答案是 100% 错误的,所以不要听他们的。听 James 的!:) PS: 我没有尝试 `collections.abc.Iterator[T] ` 你提到的,因为它依赖于 Python 3.9+,但这看起来更好。 (2认同)

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)

  • @kolypto 不,并不是 mypy 太严格。PyCharm 根本就是错误的。您应该将其注释为 Generator,装饰器将采用该 Generator 并返回 ContextManager。 (12认同)
  • 这似乎对我不起作用。Mypy 说“错误:生成器函数的返回类型应该是“Generator”或其超类型之一”和“错误:“contextmanager”的参数 1 具有不兼容的类型“Callable[[Abc, Any, Any], ContextManager[Any] ]]”;预期“Callable[...,Iterator[<nothing>]]”` (9认同)
  • 由于这一点,类型提示现在对我有用。PyCharm(2020.1.2社区版)和python 3.8。 (2认同)

Dav*_*ter 15

A. 修饰的函数的返回类型@contextmanagerIterator[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可以毫无问题地接受它。

  • 调查[潜在的欺骗](/sf/ask/3988902081/),我发现了这个答案。上下文管理器中使用的生成器的返回类型似乎应该反映上下文管理器返回的内容,即“ContextManager[_T]”。这样,我的 IDE 中的静态检查器就能够成功推断上下文变量的类型,但它无法与“Iterator”一起使用。你可以检查一下吗?我想将另一个问题标记为骗局,但就目前情况而言,这个答案并不能解决另一个问题中的问题。 (4认同)

Joe*_*Joe 5

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)

  • 您希望“assert_faster_than”和“assert_timing”的类型签名看起来相同,但您仅将“@contextmanager”应用于其中之一。我认为正确的做法是声明 `assert_faster_than(...) -> ContextManager[None]`,但是声明 `assert_timing(..) -> Iterator[None]`。 (4认同)

Ner*_*xis 5

基于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 的经验不多,我可能在这里遗漏了一些东西。