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

Python类型提示和上下文管理器,python,mypy,Python,Mypy,上下文管理器应该如何用Python类型的提示进行注释 import typing @contextlib.contextmanager def foo() -> ???: yield 这本书很少提到类型 这也不是很有帮助 还有,至少有一个例子。这是否意味着我应该使用typing.Generator[None,None,None]而不是typing.ContextManager import typing @contextlib.contextmanager def foo()

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

import typing

@contextlib.contextmanager
def foo() -> ???:
    yield
这本书很少提到类型

这也不是很有帮助

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

import typing

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

每当我不能100%确定一个函数接受什么类型时,我喜欢咨询,这是Python类型提示的规范存储库。例如,Mypy直接绑定并使用typeshed来帮助它执行类型检查

我们可以在这里找到contextlib的存根:

这有点让人难以接受,但我们关心的是:

def contextmanager(func: Callable[..., Iterator[_T]]) -> Callable[..., ContextManager[_T]]: ...
它声明decorator接受一个
可调用的[…,迭代器[\u T]]
——一个带有任意参数的函数,返回一些迭代器。因此,总之,可以这样做:

@contextlib.contextmanager
def foo() -> Iterator[None]:
    yield
那么,为什么使用
Generator[None,None,None]
也能像评论中建议的那样工作呢


这是因为
Generator
Iterator
的一个子类型——我们可以自己检查一下。因此,如果我们的函数返回一个生成器,它仍然与
contextmanager
所期望的内容兼容,因此mypy会毫无问题地接受它。

当您想要返回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]:
    ...
将在
返回断言时间(高=秒)
行上产生错误:

不兼容的返回值类型(获得“\u GeneratorContextManager[None]”,应为“迭代器[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[]
对象来代替它,并为decorator屏蔽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]:
    ...

上下文管理器包装的函数的返回类型是
Iterator[None]

from contextlib import contextmanager
from typing import Iterator

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

使用我的PyCharm,我执行以下操作以使其类型暗示生效:

from contextlib import contextmanager
from typing import ContextManager

@contextmanager
def session() -> ContextManager[Session]:
    yield Session(...)

它是一个生成器,它生成、发送和返回
None
,因此它是一个
生成器[None,None,None]
。如果你使用它作为上下文管理器并不重要。如果你知道这个特定的上下文管理器将用于什么,你可以为预期的类型进行注释,否则你几乎可以接受任何东西(甚至没有),在我的特定情况下,我只想使用上下文管理器进行日志记录(计时),因此,发送值和返回值实际上是
None
。查看a,我找到了这个答案。上下文管理器中使用的生成器的返回类型似乎应该反映上下文管理器返回的内容,即
ContextManager[\T]
。这样,IDE中的静态检查器就能够成功地推断出上下文变量的类型,而它不能与
Iterator
一起工作。你能查一下吗?我想把另一个问题标记为一个傻瓜,但就目前而言,这个答案并不能解决另一个问题中的问题。这似乎对我不起作用。Mypy说
错误:生成器函数的返回类型应该是“generator”或其超类型之一
错误:参数1到“contextmanager”的类型不兼容“Callable[[Abc,Any,Any],contextmanager[Any]]”;预期的“可调用[…,迭代器[]”
我想mypy太严格了:D目前我没有更好的注释。由于这一点,类型提示现在对我有效。PyCharm(2020.1.2社区版)和python 3.8。谢谢,这对PyCharm有帮助,但对mypy没有帮助。也许还没有一个单一的解决方案可以使这两个工具都发挥作用。您希望
assert_快于
assert_timening
的类型签名看起来相同,但您只对其中一个应用了
@contextmanager
。我认为正确的做法是声明
assert\u快于(…)->ContextManager[None]
,但是
assert\u计时(…)->迭代器[None]
from contextlib import contextmanager
from typing import ContextManager

@contextmanager
def session() -> ContextManager[Session]:
    yield Session(...)