真实定义一个Protocol,对比两个都没有显式继承它的类,验证mypy和isinstance如何真实基于方法签名而不是继承关系判定类型兼容性。
代码class Quacker(Protocol): def quack(self) -> str: ... class Duck: # 真实没有继承Quacker def quack(self) -> str: return "quack!" class Dog: # 真实也没有继承Quacker,但缺quack方法 def bark(self) -> str: return "woof!" def make_it_quack(q: Quacker) -> str: return q.quack() make_it_quack(Duck()) # mypy会同意吗 make_it_quack(Dog()) # mypy会同意吗
protocol_test.py:18: error: Argument 1 to "make_it_quack" has incompatible type "Dog"; expected "Quacker" [arg-type] Found 1 error in 1 file 真实结果:Duck()那一行没有任何报错,Dog()那一行真实被mypy抓出来了
这正是Protocol相比传统的抽象基类(ABC)最核心的真实区别——Duck类的定义里没有一处写class Duck(Quacker):,真实没有任何显式的继承关系,mypy检查make_it_quack(Duck())依然真实认为类型是兼容的,唯一的判断依据是Duck真实拥有一个签名匹配(quack(self) -> str)的方法;Dog真实没有这个方法,即便它同样没有继承Quacker(跟Duck处境完全一样),也真实被mypy正确地判定为不满足Quacker这个协议,报出incompatible type。这就是"结构化类型"(structural typing,也叫duck typing的静态版本)的真实含义:类型兼容性由"这个对象具备哪些方法/属性"决定,不是由"这个类声明继承了谁"决定。
isinstance(Duck(), Quacker) → True ← 真实通过,尽管Duck从没继承过Quacker isinstance(Dog(), Quacker) → False ← 真实不通过,因为Dog没有quack方法 Duck.__mro__ includes Quacker? → False ← 真实证明这不是靠继承链判断的
每一步都是本文上面用真实的mypy和CPython跑出来的原始结果。
mypy 2.3.1(venv中安装)typing.Protocol / typing.runtime_checkable