鸭子类型 (Duck Typing)
鸭子类型按行为能力而非名义类型协作;在 Python 中与特殊方法、Protocol 静态检查形成互补。
#type / concept
#status / evergreen
#tech / dev
#resource / python
#resource / javascript
#tech / lang / python
[!info] 关联笔记
鸭子类型 (Duck Typing)
这个概念为什么出现
框架与库常说:“给我一个 file-like / iterable / awaitable 就行”。调用方不关心它是不是某个基类的子类,只关心能不能协作。这就是鸭子类型:
若走起来像鸭子、叫起来像鸭子,就当鸭子用。
[!abstract] 一句话理解 鸭子类型以运行时行为能力决定能否协作,而不是以名义继承身份;Python 用协议与特殊方法把它变成语言习惯。
最小可运行示例
先把示例放进业务场景,再看代码:
场景:导出服务接受任何“能提供只读行迭代”的来源
# duck_typing_export.py
# 业务意图:export_lines 不要求具体类,只要可迭代文本行。
# 教学点:
# - 面向协议而非基类;
# - list 与自定义对象都能协作;
# - 缺少协议则运行时失败。
from typing import Iterable
def export_lines(lines: Iterable[str]) -> str:
# 业务:把多行正文合并为导出文本。
return "\n".join(line.rstrip("\n") for line in lines)
class TicketLog:
def __init__(self, rows: list[str]) -> None:
self._rows = rows
def __iter__(self):
return iter(self._rows)
def main() -> None:
print(export_lines(["a", "b"]))
print(export_lines(TicketLog(["t1", "t2"])))
if __name__ == "__main__":
main()
建议运行:
python duck_typing_export.py
期望输出:
a
b
t1
t2
结合场景再看三个关注点
- 函数注解写 Iterable 表达协议意图。
- 自定义类只需
__iter__,无需继承。 - 静态检查可用
Protocol提前抓缺口(见专篇)。
核心概念与准确模型
| 维度 | 鸭子类型 | 名义类型 |
|---|---|---|
| 判断 | 行为/结构 | 声明的继承/实现 |
| 时刻 | 多用时/运行时 | 编译期/检查期 |
| Python | 协议、dunder | ABCs 可名义可虚拟 |
Python 中的落点:
- 特殊方法协议(容器、上下文、异步)
collections.abctyping.Protocol(静态结构类型)
边界情况与反直觉行为
- 错误发现偏晚:缺方法到调用才炸。
- 同名不同义:都有
read但语义不同。 - 过度鸭子让可读性下降——公开 API 仍要文档化协议。
常见误区
[!warning] 常见误区:动态语言所以不用类型 错误理解:鸭子类型排斥标注。
正确模型:运行时鸭子 + 静态 Protocol/注解可并存。
工程实践
- 文档写清最小协议(“需要
__iter__产出 str”)。 - 边界校验可显式检查关键方法。
- 公共库优先小协议,避免强迫继承框架基类。
本节总结
鸭子类型是 Python 协作的默认文化。用协议思考,用特殊方法实现,用 Protocol/测试加固。
自测题
- 鸭子类型与
isinstance强制基类有何不同? - 如何降低“用时才发现缺方法”的风险?
参考答案
- 前者看行为,后者看名义身份。
- Protocol/类型检查、契约测试、文档化最小方法集。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| Glossary – duck-typing | 术语 | 官方定义 |
| typing.Protocol | 文档 | 静态结构类型 |