MyPy 检查 typing.Protocol 与 Python 3.7 支持

MyPy checking typing.Protocol with Python 3.7 Support

我有一个项目需要支持 Python 3.7,但我想使用 typing.Protocol,它是在 3.8 中添加的。为了支持 3.7,我有一小段仅使用 object:

的后备代码
import typing

_Protocol = getattr(typing, 'Protocol', object)

class Foo(_Protocol):
    def bar(self) -> int:
        pass

这一切都符合我的预期。问题是,当 运行 MyPy 时,出现以下错误:

test.py:5: error: Variable "test._Protocol" is not valid as a type
test.py:5: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases
test.py:5: error: Invalid base class "_Protocol"

该错误消息中链接的 "Variables vs type aliases" 部分表明我应该用 : typing.Type[object] 注释 _Protocol (这不起作用)或使用 typing.TypeAlias (这不是'直到 Python 3.9)。

如何向 MyPy 指示 _Protocol 作为类型有效?


我尝试的另一个解决方法是 "Python version and system platform checks":

if sys.version_info >= (3, 8):
    _Protocol = typing.Protocol
else:
    _Protocol = object

然而,这以同样的错误结束。

使用typing_extensionstyping.TYPE_CHECKING仅在type-checking代码时导入typing_extensions

import typing

if typing.TYPE_CHECKING:
    from typing_extensions import Protocol
else:
    Protocol = object


class Foo(Protocol):
    def bar(self) -> int:
        ...

typing_extensions checks Python 版本并使用 typing.Protocol 版本 >=3.8:

# 3.8+
if hasattr(typing, 'Protocol'):
    Protocol = typing.Protocol
# 3.7
else:
    ...

MyPy 似乎可以正确理解 Protocol 作为导入语句的一部分:

if sys.version_info >= (3, 8):
    from typing import Protocol as _Protocol
else:
    _Protocol = object

class Foo(_Protocol):
    def bar(self) -> int: ...

MyPy 仍会将 Foousage 标记为 Python 3.7 中的类型规范,因此您需要类似的解决方法来使用它:

if sys.version_info >= (3, 8):
    _Foo = Foo
else:
    _Foo = typing.Any

def bar(x: _Foo):
    pass

值得注意的是,MyPy 理解更聪明的东西,比如 _Foo = Foo if sys.version_info >= (3, 8) else typing.Any -- 你必须使用简单的格式。