使用 mypy 对 Protocol 子类的 python 函数签名进行类型检查

And*_*art 6 python types mypy

有没有一种方法可以安全地对协议子类的 python 类进行类型检查?

\n

如果我定义具有特定方法函数签名的协议,则隐式子类必须定义具有兼容签名的方法:

\n
# protocols.py\n\nfrom abc import abstractmethod\nfrom dataclasses import dataclass\nfrom typing import Protocol\n\nclass SupportsPublish(Protocol):\n    @abstractmethod\n    def publish(self, topic: str):\n        ...\n\ndef publish(m: SupportsPublish):\n    m.publish("topic")\n\n@dataclass\nclass Publishable:\n    foo: str = "bar"\n\n    def publish(self):\n        print(self)\n\npublish(Publishable())\n\n# \xe2\x9c\x97 mypy protocols.py       \n# protocols.py:24: error: Argument 1 to "publish" has incompatible type "Publishable"; expected "SupportsPublish"  [arg-type]\n# protocols.py:24: note: Following member(s) of "Publishable" have conflicts:\n# protocols.py:24: note:     Expected:\n# protocols.py:24: note:         def publish(self, topic: str) -> Any\n# protocols.py:24: note:     Got:\n# protocols.py:24: note:         def publish(self) -> Any\n# Found 1 error in 1 file (checked 1 source file)\n
Run Code Online (Sandbox Code Playgroud)\n

但是,如果我显式 subtype SupportsPublish, mypy 不会报告类型错误:

\n
...\n@dataclass\nclass Publishable(SupportsPublish):\n...\n\n# \xe2\x9c\x97 mypy protocols.py\n# Success: no issues found in 1 source file\n
Run Code Online (Sandbox Code Playgroud)\n

根据PEP 的简介,我希望类型检查器能够找到函数签名不匹配的地方:

\n
\n

请注意,显式子类型和隐式子类型之间没有什么区别,显式子类化的主要好处是免费获得一些协议方法\xe2\x80\x9c\xe2\x80\x9d。此外,类型检查器可以静态验证该类是否确实正确实现了协议:

\n
\n

这是我的环境:

\n
\n> mypy --version\nmypy 1.3.0 (compiled: yes)\n> python --version\nPython 3.9.17\n
Run Code Online (Sandbox Code Playgroud)\n

我希望 mypy 指出函数签名不匹配。

\n

jua*_*aga 3

我只是想指出,正如评论中所确定的那样,如果您显式 subtype SupportsPublish, mypy 不会报告类型错误,那么实际上并不正确。

问题是你没有对你的方法进行类型注释,这本质上告诉 mypy“不要检查这个”。

如果你这样做,例如:

from dataclasses import dataclass
from typing import Protocol

class SupportsPublish(Protocol):
    def publish(self, topic: str) -> None:
        ...

def publish(m: SupportsPublish):
    m.publish("topic")

@dataclass
class Publishable:
    foo: str = "bar"

    def publish(self) -> None:
        print(self)

Run Code Online (Sandbox Code Playgroud)

然后 mypy抱怨:

(py311) Juans-MBP:~ juan$ mypy foo.py
foo.py:18: error: Argument 1 to "publish" has incompatible type "Publishable"; expected "SupportsPublish"  [arg-type]
foo.py:18: note: Following member(s) of "Publishable" have conflicts:
foo.py:18: note:     Expected:
foo.py:18: note:         def publish(self, topic: str) -> None
foo.py:18: note:     Got:
foo.py:18: note:         def publish(self) -> None
Found 1 error in 1 file (checked 1 source file)
Run Code Online (Sandbox Code Playgroud)

因为这只是使用方法重写进行常规子类化的要求

如果您不打算以完整--strict模式运行 mypy,至少以某种方式(通过调用它的方式或使用 a mypy.ini)确保您拥有--disallow-untyped-defs--disallow-untyped-calls