python通过protocol和鸭子类型实现接口思想:protocol为类型检查器提供结构化行为契约,零运行时开销;鸭子类型依赖运行时方法存在性,灵活但错误延迟暴露;二者互补,protocol提升可维护性,鸭子类型保障动态性。

Python 中没有传统意义上的“接口”语法,但通过协议(Protocol)和鸭子类型(Duck Typing),可以自然、灵活地实现接口思想——关注“能做什么”,而非“是什么类型”。
协议(Protocol):显式定义行为契约
Python 3.8+ 引入了 typing.Protocol,允许你用结构化方式声明一个类型需要具备哪些方法或属性,而不强制继承。它不是运行时检查的抽象基类,而是为类型检查器(如 mypy)提供提示,同时保持运行时零开销。
例如,定义一个可迭代且支持长度计算的容器协议:
from typing import Protocol <p>class SizedIterable(Protocol): def <strong>iter</strong>(self): ... def <strong>len</strong>(self) -> int: ...</p><p>def count_items(container: SizedIterable) -> int: return len(container) # 类型检查器知道 container 有 <strong>len</strong>
只要某个类实现了 __iter__ 和 __len__,即使没继承任何基类,也能被当作 SizedIterable 使用,mypy 也会认可。
立即学习“Python免费学习笔记(深入)”;
鸭子类型:运行时的隐式接口
这是 Python 最核心的接口思想:“如果它走起来像鸭子,叫起来像鸭子,那它就是鸭子”。不依赖类型声明,只依赖对象是否具备所需方法。
- 函数调用时,只检查对象有没有对应方法(如 obj.read()),不关心 obj 是 io.StringIO、文件对象还是自定义类
- 常见例子:json.dump(obj, fp) 只要求 fp 有 write() 方法;for x in obj: 只要求 obj 可迭代(有 __iter__ 或 __getitem__)
- 优势是高度解耦和动态性;风险是错误可能延迟到运行时才暴露
协议 + 鸭子类型的协同使用
二者不是互斥,而是互补:鸭子类型支撑运行时灵活性,协议增强开发期可维护性与协作可靠性。
- 写库时,用 Protocol 明确标注参数期望的行为,让使用者一目了然该传什么“样子”的对象
- 写业务逻辑时,按需实现协议方法,无需修改已有继承体系,也无需引入额外依赖
- 测试时,可轻松构造轻量“伪对象”(mock-like)满足协议,比如只实现 __call__ 就能当函数用
对比 ABC(抽象基类):何时选谁?
ABC(如 collections.abc.Iterable)适合需要运行时类型检查、强制注册或想提供默认实现的场景;而 Protocol 更轻量、更聚焦“形状”,适合强调组合与结构一致性的接口建模。
- 如果你希望 isinstance(obj, Iterable) 返回 True,用 ABC
- 如果你只想告诉类型检查器“这个参数得有 __next__”,用 Protocol
- 如果只是临时拼凑行为、不想污染类定义,直接靠鸭子类型最简单










