应选Protocol当需静态类型检查的结构化契约,即只要对象具备指定方法签名就兼容,无需继承或修改源码;ABC适用于运行时强制接口契约,要求显式继承并支持isinstance检查。

Python 的 Protocol 不是运行时机制,它只在类型检查阶段起作用(比如用 mypy),不能替代 isinstance 或运行时行为验证。
什么时候该用 Protocol 而不是抽象基类(ABC)?
当你只想表达“只要对象有某个方法签名,就视作兼容”,而不想强制继承或注册——比如写一个通用序列处理器,不关心它是 list、tuple 还是自定义类,只要它实现了 __len__ 和 __getitem__ 就行。
-
Protocol是结构化类型(structural typing),ABC 是名义类型(nominal typing) - 用
Protocol无需修改被适配类的源码,ABC 要求显式继承或register() -
Protocol不能用于isinstance或issubclass判断,否则会报TypeError: issubclass() argument 2 cannot be a parameterized generic - 如果协议含可选方法,得用
@typing.runtime_checkable+typing.Optional,但依然不能靠isinstance检查
如何让 mypy 正确识别 Protocol 实现?
常见错误是协议字段/方法签名和实际对象不一致,比如参数名不同、缺失 self、返回类型写错,mypy 就会静默忽略匹配(即不报错,但也不认为满足协议)。
- 协议中方法必须带
self,即使你希望它像静态方法一样调用;否则mypy认为这是普通函数,不参与实例匹配 - 参数名要一致:协议写
def process(self, data: str) -> int:,实现类里写def process(self, x: str) -> int:会被认为不兼容 - 协变/逆变需显式声明:比如协议中
def add(self, item: T) -> None:,若T是泛型,要用typing.Generic[T]并继承Protocol,否则mypy可能误判 - 运行时想调试是否匹配?只能手动检查
hasattr(obj, 'method_name')和callable(getattr(obj, 'method_name')),别指望Protocol提供反射支持
Protocol 和 TypedDict、NamedTuple 的关键区别
三者都用于描述结构,但用途和约束完全不同:
立即学习“Python免费学习笔记(深入)”;
-
Protocol描述行为接口(方法+属性),支持方法重载、泛型、继承,仅用于类型检查 -
TypedDict描述键值对字典结构,键名固定、类型明确,运行时仍是dict,可直接实例化 -
NamedTuple是不可变数据容器,有真实类对象,支持isinstance,但字段必须全部指定,不能“只实现部分” - 混用风险:把
Protocol当TypedDict用(比如只定义字段没方法),mypy仍会尝试检查方法存在性,导致误报
真正容易被忽略的是:协议一旦泛型化,就必须在使用处显式标注类型参数,比如 MyProtocol[str];写成 MyProtocol 会被 mypy 当作原始类型(raw type),失去类型安全。而且,协议中的方法若引用了未声明的泛型变量,mypy 不会报错,但检查结果不可靠。


















