鸭子类型是python实现多态最自然的方式,只关注对象是否有所需方法而非其类型;运行时动态调用,零开销、兼容性强,但需靠文档和测试保障方法签名与返回值一致性。

duck typing 是 Python 实现多态最自然、最常用的方式——它不依赖继承或接口声明,只关心“对象有没有那个方法”,而不是“它是不是某个类的实例”。
为什么直接用 duck typing 就能实现多态
Python 在调用方法时完全不检查对象类型,只在运行时尝试访问属性或方法。只要对象有 export 方法,你就能传给 export_data 函数;只要它有 quack,make_it_quack 就能跑通。这种“行为即类型”的逻辑,就是鸭子类型的核心。
- 常见错误现象:写了个函数,加了类型提示如
def process(obj: Exporter),结果运行时报TypeError: Exporter is not a concrete class——这是强行套 Java 思维,反而卡住自己 - 使用场景:快速原型、脚本工具、配置驱动逻辑(比如插件系统里加载任意导出器)
- 性能影响:零额外开销,比 ABC 或 Protocol 更轻量
- 兼容性:Python 2.7 起就支持,无版本限制
class PDFExporter:
def export(self, data):
print(f"PDF: {data}")
<p>class CSVExporter:
def export(self, data):
print(f"CSV: {data}")</p><p>def export_data(exporter, data):
exporter.export(data) # 不检查 exporter 是什么类,只看有没有 export</p><p>export_data(PDFExporter(), "report")
export_data(CSVExporter(), "sales")
</p>
duck typing 下怎么避免 AttributeError
不是所有“看起来像鸭子”的对象都真能叫出声。调用前没做兜底,容易在运行时崩。
- 用
hasattr(obj, "export")提前判断,但略啰嗦 - 更推荐用
try/except AttributeError包裹调用,符合 Python 的 EAFP(Easier to Ask for Forgiveness than Permission)原则 - 如果是公共 API,建议在文档里明确写出“需实现
export(self, data)方法”,而不是靠类型注解约束 - 别在函数开头写
if not hasattr(...)+raise TypeError——这等于自己造了个弱版 ABC,又没 ABC 的 IDE 支持和强制力
和 Protocol、ABC 的关键区别在哪
三者都能达成“同一函数处理多种类型”的效果,但设计意图和适用阶段不同:
-
duck typing:写脚本、内部工具、快速迭代时首选。不写任何协议定义,靠约定和测试兜底 -
Protocol(Python 3.8+):需要静态类型检查(如 mypy)配合时用。IDE 能提示缺失方法,但运行时不强制 -
ABC:团队协作、库开发、强契约场景。子类忘记实现@abstractmethod会在实例化时报错,不可绕过
from typing import Protocol <p>class Exporter(Protocol): def export(self, data: str) -> None: ...</p><p>def export_data(exporter: Exporter, data: str) -> None: exporter.export(data) # mypy 会检查传入对象是否满足 Exporter 协议 </p>
注意:Exporter 这个 Protocol 类型本身不能被实例化,也不参与运行时逻辑——它只活在类型检查阶段。
容易被忽略的边界点
duck typing 看似自由,但真正落地时最容易栽在两个地方:一是方法签名不一致(比如一个 export(self, data),另一个是 export(self, data, format="pdf")),二是返回值语义混乱(比如有的 export 返回路径字符串,有的返回布尔成功状态)。这些不会触发运行时错误,却会让上层逻辑悄悄出错。解决办法不是加类型系统,而是写清楚文档 + 补充单元测试覆盖不同实现。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











