如何用mypy安全检查Protocol子类的Python函数签名?
当定义带有特定方法签名的Protocol时,隐式实现该协议的类会被mypy正确检测出签名不兼容问题,但显式继承该Protocol的类即使方法签名不匹配,mypy默认也不会报错,具体场景如下:
隐式实现的情况(mypy正确报错)
# protocols.py from abc import abstractmethod from dataclasses import dataclass from typing import Protocol class SupportsPublish(Protocol): @abstractmethod def publish(self, topic: str): ... def publish(m: SupportsPublish): m.publish("topic") @dataclass class Publishable: foo: str = "bar" def publish(self): print(self) publish(Publishable())
运行mypy protocols.py会得到错误:
protocols.py:24: error: Argument 1 to "publish" has incompatible type "Publishable"; expected "SupportsPublish" [arg-type]
protocols.py:24: note: Following member(s) of "Publishable" have conflicts:
protocols.py:24: note: Expected:
protocols.py:24: note: def publish(self, topic: str) -> Any
protocols.py:24: note: Got:
protocols.py:24: note: def publish(self) -> Any
Found 1 error in 1 file (checked 1 source file)
显式继承的情况(mypy默认不报错)
将Publishable改为显式继承SupportsPublish:
@dataclass class Publishable(SupportsPublish): foo: str = "bar" def publish(self): print(self)
运行mypy protocols.py会提示Success: no issues found in 1 source file,但这不符合PEP 0544中类型检查器应验证显式实现正确性的要求。
解决方案
1. 启用mypy的协议成员检查选项
mypy默认关闭了对显式继承Protocol的类的成员签名检查,只需启用--protocol-member-checking选项(或包含该选项的--strict模式)即可触发检查:
mypy --protocol-member-checking protocols.py
此时mypy会正确检测出签名不匹配的错误,输出与隐式实现时一致的报错信息。
2. 结合运行时检查(可选)
若需要运行时也能验证类是否符合Protocol,可以给SupportsPublish添加@runtime_checkable装饰器:
from typing import Protocol, runtime_checkable @runtime_checkable class SupportsPublish(Protocol): @abstractmethod def publish(self, topic: str): ...
之后可以用isinstance(Publishable(), SupportsPublish)在运行时检查,但注意运行时检查仅验证方法是否存在,无法检测签名兼容性,仍需配合静态类型检查确保签名正确。
内容的提问来源于stack exchange,提问作者Andrew Stewart

