在 3.5 中键入 __exit__ 在运行时失败,但类型检查

问题描述 投票:0回答:1

__exit__
的正确类型签名是什么?我有以下内容:

from types import TracebackType
from typing import Optional, Type

class Foo:
    def __enter__(self) -> 'Foo':
        return self

    def __exit__(self, exc_type: Optional[Type[BaseException]],
                 exc_value: Optional[BaseException],
                 traceback: Optional[TracebackType]) -> bool:
        return False

在最近的 mypy (0.560) 上,此类型检查与

--strict
(我对这个签名有一定的信心,因为我从 typeshed 的内部窃取了它)。

当使用 python 3.6 运行此脚本时,正如预期的那样,什么也没有发生。但是当使用 3.5.2 运行时,我们得到一个异常:

Traceback (most recent call last):
  File "/home/student/mypy_test/test.py", line 4, in <module>
    class Foo: #(ContextManager['Foo']):
  File "/home/student/mypy_test/test.py", line 8, in Foo
    def __exit__(self, exc_type: Optional[Type[BaseException]],
  File "/usr/lib/python3.5/typing.py", line 649, in __getitem__
    return Union[arg, type(None)]
  File "/usr/lib/python3.5/typing.py", line 552, in __getitem__
    dict(self.__dict__), parameters, _root=True)
  File "/usr/lib/python3.5/typing.py", line 512, in __new__
    for t2 in all_params - {t1} if not isinstance(t2, TypeVar)):
  File "/usr/lib/python3.5/typing.py", line 512, in <genexpr>
    for t2 in all_params - {t1} if not isinstance(t2, TypeVar)):
  File "/usr/lib/python3.5/typing.py", line 1077, in __subclasscheck__
    if super().__subclasscheck__(cls):
  File "/home/student/.local/share/virtualenvs/sf_cs328-crowdsourced-QAuuIxFA/lib/python3.5/abc.py", line 225, in __subclasscheck__
    for scls in cls.__subclasses__():
TypeError: descriptor '__subclasses__' of 'type' object needs an argument

如果去掉参数直到异常消失,我们发现问题类型是第一种:

exc_type: Optional[Type[BaseException]]

注意: 要让它在类型签名不匹配时抱怨(当使用 mypy 运行时),您需要将

class Foo:
更改为
class Foo(ContextManager['Foo'])
。我没有在代码片段中执行此操作,因为 Python 3.5.2 中的
typing
缺少
Coroutine
Awaitable
ContextManager
等(这是旧发行版的 LTS 版本中的版本)。我在这里写了一个解决方法:https://stackoverflow.com/a/49952293/568785。所以我想,完整的可重现示例是:

# Workaround for ContextManager missing in 3.5.2 typing
from typing import Any, TypeVar, TYPE_CHECKING
try:
    from typing import ContextManager
except ImportError:
    class _ContextManager:
        def __getitem__(self, index: Any) -> None:
            return type(object())

    if not TYPE_CHECKING:
        ContextManager = _ContextManager()

# The actual issue:
from types import TracebackType
from typing import Optional, Type

class Foo:
    def __enter__(self) -> 'Foo':
        return self

    def __exit__(self, exc_type: Optional[Type[BaseException]],
                 exc_value: Optional[BaseException],
                 traceback: Optional[TracebackType]) -> bool:
        return False

我已经验证,不继承

ContextManager
在 Python 3.5.2 中运行仍然会产生错误(所以这个异常不是这个 hack 的产物,它是 3.5.2 运行时的类型库不喜欢签名的产物
__exit__
)。

据推测,这是 Python 3.5 的

typing
库中的另一个错误。有没有明智的方法来解决这个问题?

python python-typing mypy contextmanager
1个回答
1
投票

我现在使用的一个丑陋解决方法是使用

TYPE_CHECKING
来确定我是否应该伪造类型:

from typing import Type, TYPE_CHECKING

if TYPE_CHECKING:
    BaseExceptionType = Type[BaseException]
else:
    BaseExceptionType = bool # don't care, as long is it doesn't error

然后你可以这样做:

def __exit__(self, exc_type: Optional[BaseExceptionType],
             exc_value: Optional[BaseException],
             traceback: Optional[TracebackType]) -> bool:
    return False

已针对 Python 3.5.2 和 mypy 0.560 进行验证。

这当然会破坏任何 RTTI,但据我所知,RTTI 是 PEP 的一部分,直到 3.6 或 3.7 才(实验性地)落地。显然,这确实打破了

typing.get_type_hints()

© www.soinside.com 2019 - 2024. All rights reserved.