鸭子类型 (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

结合场景再看三个关注点

  1. 函数注解写 Iterable 表达协议意图。
  2. 自定义类只需 __iter__,无需继承。
  3. 静态检查可用 Protocol 提前抓缺口(见专篇)。

核心概念与准确模型

维度鸭子类型名义类型
判断行为/结构声明的继承/实现
时刻多用时/运行时编译期/检查期
Python协议、dunderABCs 可名义可虚拟

Python 中的落点:

  • 特殊方法协议(容器、上下文、异步)
  • collections.abc
  • typing.Protocol(静态结构类型)

边界情况与反直觉行为

  1. 错误发现偏晚:缺方法到调用才炸。
  2. 同名不同义:都有 read 但语义不同。
  3. 过度鸭子让可读性下降——公开 API 仍要文档化协议。

常见误区

[!warning] 常见误区:动态语言所以不用类型 错误理解:鸭子类型排斥标注。
正确模型:运行时鸭子 + 静态 Protocol/注解可并存。

工程实践

  • 文档写清最小协议(“需要 __iter__ 产出 str”)。
  • 边界校验可显式检查关键方法。
  • 公共库优先小协议,避免强迫继承框架基类。

本节总结

鸭子类型是 Python 协作的默认文化。用协议思考,用特殊方法实现,用 Protocol/测试加固。

自测题

  1. 鸭子类型与 isinstance 强制基类有何不同?
  2. 如何降低“用时才发现缺方法”的风险?
参考答案
  1. 前者看行为,后者看名义身份。
  2. Protocol/类型检查、契约测试、文档化最小方法集。

延伸阅读与资料来源

资料类型支撑内容
Glossary – duck-typing术语官方定义
typing.Protocol文档静态结构类型
创建于 2026/4/1 更新于 2026/7/15