protocol是结构化类型协议,仅用于静态类型检查;与abc不同,它不依赖继承或注册,只验证方法签名是否匹配,且默认不支持运行时isinstance检查。

Python 的 Protocol 不是运行时类型检查工具,它只在静态类型检查(如 mypy)中生效;你写一个类“看起来像”某个 Protocol,但没显式继承或注册,mypy 就会报错——除非你用 typing.runtime_checkable 且配合 isinstance,但那已脱离 Protocol 本意。
Protocol 是什么,和 ABC 有什么根本区别?
Protocol 描述的是“结构契约”,不是“类型继承关系”。它不关心你是不是它的子类,只关心你有没有那些方法、属性、签名是否匹配。而 ABC(抽象基类)靠 __subclasshook__ 或显式 register / 继承来建立类型归属,是运行时可感知的。
- 定义
class Drawable(Protocol)只是告诉类型检查器:“任何有draw(self) -> None的对象,我都认作Drawable” -
class Circle: def draw(self) -> None: ...—— 这个类甚至不知道Drawable的存在,mypy 仍会接受它作为Drawable参数传入 - 但
isinstance(Circle(), Drawable)默认会报错:因为Drawable不是runtime_checkable,没有运行时协议检查能力
怎么写出能被 mypy 正确识别的 Protocol?
关键在签名一致性:方法名、参数名(含 */**)、返回类型、是否为 @staticmethod 或 @classmethod 都必须严格匹配。mypy 不做动态推导,只做字面比对。
- 不要省略参数名:
def render(self, fmt)和def render(self, f)被视为不同协议成员 - 可选参数要显式标注默认值:
def save(self, path: str, compress: bool = False)—— 如果实现类漏掉= False,mypy 可能不认 - 变长参数必须一致:
*args: str和*a: str可以,但*args和*args: Any在严格模式下可能触发警告 - 属性声明用
attr_name: type,不能带赋值(那是数据类语法),例如name: str,不是name: str = "x"
为什么 isinstance(obj, MyProtocol) 总是 False?
因为标准 Protocol 不支持运行时检查。这是设计使然:结构化类型本就不该依赖 isinstance 做逻辑分支。如果你真需要运行时判断,必须加 @runtime_checkable 装饰器,且所有成员必须是普通实例方法/属性(不支持泛型参数、重载、描述符等高级特性)。
- 正确写法:
from typing import Protocol, runtime_checkable <p>@runtime_checkable class Indexable(Protocol): def <strong>getitem</strong>(self, i: int) -> str: ... def <strong>len</strong>(self) -> int: ...</p><p>isinstance("hello", Indexable) # True isinstance([1, 2], Indexable) # True</p> - 错误写法:
class GenericProto(Protocol[T])——@runtime_checkable不支持泛型参数,运行时报TypeError - 更隐蔽的坑:如果协议里有
@property,运行时检查可能失败,因为getattr(obj, "prop")触发了实际计算,而协议本意只是“能访问”,不是“访问后不抛错”
真正容易被忽略的是:Protocol 的泛型参数(如 class Mapper[In, Out](Protocol))在 mypy 中只参与类型推导,不生成新类型;你无法用 isinstance(x, Mapper[str, int]),甚至连 Mapper[str, int] 本身都不能作为运行时对象使用——它只是类型检查阶段的占位符。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











