typing.Protocol 是鸭子类型契约,仅需结构匹配即可被静态类型检查器识别,不依赖继承;运行时需显式添加@runtime_checkable才支持isinstance判断。

typing.Protocol 本质是鸭子类型契约,不是继承关系
Protocol 不要求类显式继承或注册,只要实例具备声明的方法签名和属性,就被视为该协议的实现。这和 abc.ABC 完全不同——后者强制继承与 abstractmethod 实现,而 Protocol 只做静态检查时的“形状匹配”。
常见错误是试图用 isinstance(obj, MyProtocol) 判断,结果总返回 False:因为 Protocol 默认不启用运行时检查。若需运行时支持,必须显式继承 typing.runtime_checkable 装饰器。
- 静态类型检查(如 mypy)能识别符合结构的对象,无需任何运行时开销
- 运行时判断需加
@typing.runtime_checkable,否则isinstance()永远不成立 - 协议中方法不写
self参数类型(mypy 会自动推导),但需标注返回值和参数类型
定义带可选方法的 Protocol 要用 typing.Optional + overload 或 @overload
Protocol 默认所有方法都必须存在。想表达“有这个方法更好,没有也行”,不能直接标 Optional[Callable] 属性——mypy 会报错“protocol requires attribute”。正确做法是用 @overload 声明多个调用签名,或拆成两个协议(如 Readable 和 SeekableReadable)。
例如想描述“可能支持 seek() 的文件类”:
立即学习“Python免费学习笔记(深入)”;
from typing import Protocol, overload, Optional <p>class FileLike(Protocol): def read(self, size: int = -1) -> bytes: ...</p><pre class="brush:php;toolbar:false;">@overload def seek(self, offset: int) -> int: ... @overload def seek(self, offset: int, whence: int) -> int: ...
注意:这里 @overload 是为了告诉类型检查器 seek 可接受两种调用形式,不是为了实现多分派;实际类中仍只需一个 seek 方法。
Protocol 与泛型一起用时,必须用 typing.TypeVar 绑定
如果协议方法要操作泛型数据(比如 def get_item(self, key: str) -> T),不能直接写 T,必须先声明 TypeVar 并用 Generic[T] 继承协议。否则 mypy 报 Invalid type "T"。
- 协议本身要继承
Generic[T],且在类名后加方括号:class Container(Protocol[T]): - 方法中可直接使用
T作为参数或返回类型 - 实现类不需要显式继承
Container[str],只要结构匹配即可被接受
示例:
from typing import Protocol, Generic, TypeVar
<p>T = TypeVar('T')</p><p>class Indexable(Protocol[T]):
def <strong>getitem</strong>(self, i: int) -> T: ...</p><h1>这个 list 实例会被 mypy 认为满足 Indexable[str]</h1><p>data: Indexable[str] = ["a", "b"]
嵌套 Protocol 或组合多个 Protocol 用 Protocol[...] 语法不合法,改用 class 组合
不能写 class A(Protocol[B, C]) 或 class D(Protocol[A & B])——Python 语法不支持。想表达“同时满足多个协议”,应使用 typing.Protocol 的多重继承写法(仅用于类型提示,非运行时):
class Readable(Protocol):
def read(self) -> bytes: ...
<p>class Seekable(Protocol):
def seek(self, offset: int) -> int: ...</p><p>class RandomAccess(Readable, Seekable, Protocol): # ✅ 合法
pass
这样定义后,RandomAccess 就等价于“既是 Readable 又是 Seekable”的结构约束。注意:组合后的协议依然不支持运行时 isinstance,除非每个父协议都加了 @runtime_checkable,且组合类也加上。
容易忽略的是:Protocol 组合不传递 @runtime_checkable,哪怕父类都加了,子类也得单独加一次装饰器才生效。


















