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

时间:2018-04-09 13:05:04

标签: python mypy

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

typing.ContextManager

documentation on contextlib并未提及类型。

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

还有typing.Generator,至少有一个例子。这是否意味着我应该使用import typing @contextlib.contextmanager def foo() -> typing.Generator[None, None, None]: yield 而不是<?php $title = 'Title here'; $short_url = 'http://shorturl.co'; $url = 'http://fullurl.com'; $twitter_params = '?text=' . urlencode($title) . '+-' . '&amp;url=' . urlencode($short_url) . '&amp;counturl=' . urlencode($url) . ''; $link = "http://twitter.com/share" . $twitter_params . ""; ?> <a href="<?php echo $link; ?>">Share on Twitter</a> <?php // rest of your code ?>

arr.push(val.players)

5 个答案:

答案 0 :(得分:7)

每当我不能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]]: ...

这有点压倒性,但我们关心的是这一行:

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会毫无问题地接受它。

答案 1 :(得分:3)

使用我的PyCharm,我可以执行以下操作来使其类型提示起作用:

Published

答案 2 :(得分:1)

要返回contextmanager的引用时,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]:
    ...

return assert_timing(high=seconds)行上将产生错误:

Incompatible return value type (got "_GeneratorContextManager[None]", expected "Iterator[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[]对象,并让装饰器的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]:
    ...

答案 3 :(得分:1)

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

from contextlib import contextmanager
from typing import Iterator

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

答案 4 :(得分:1)

基于 PEP-585,正确的注释类型似乎是 AbstractContextManager(参见 https://www.python.org/dev/peps/pep-0585/#implementation)。比你可以使用以下代码:

import contextlib

@contextlib.contextmanager
def foo() -> contextlib.AbstractContextManager[None]:
    yield

这是与 PyCharm 一起正确工作的唯一解决方案(与 typing.ContextManager 一起使用,但该解决方案应从 Python 3.9 中弃用)。当您在非常有用的 with 语句(类型提示)中使用它时,它会正确地帮助您。

但是当我回到最初的问题(“应该如何用 Python 类型提示注释上下文管理器?”)时,这取决于。从我的角度来看,正确的应该是我提到的那个。但这似乎不适用于 mypy(还)。有一些关于此 PEP 的更新(请参阅 https://github.com/python/mypy/issues/7907),但由于我对 mypy 的经验不多,我可能在这里遗漏了一些东西。