鸭子类型是Python多态的自然实现方式,不依赖接口或继承,只关注对象是否具备所需方法;len()等内置函数通过调用__len__()等特殊方法实现泛型支持,强调行为契约而非类型检查。

鸭子类型不是接口,也不需要继承;只要对象有对应方法,就能被当作目标类型使用——这是 Python 多态最自然的实现方式。
为什么 len() 能对列表、字符串、字典都有效?
因为 len() 内部只调用对象的 __len__() 方法,不检查类型。只要实现了这个特殊方法,就“算数”。
- 列表有
__len__()→ 返回元素个数 - 自定义类只要定义了
def __len__(self): return 42,len(my_obj)就能运行 - 没实现会抛
TypeError: object of type 'X' has no len(),而不是类型检查失败 - 这种“行为契约”比类型声明更轻量,也更灵活
写函数时别写 isinstance(x, list),改用“有没有 append”
判断类型不如判断能力。比如想让函数支持任意可添加元素的容器:
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
- ❌ 错误示范:
if isinstance(data, (list, deque)):—— 每新增一种类型就得改条件 - ✅ 正确思路:
if hasattr(data, 'append') and callable(getattr(data, 'append')): - 更 Pythonic 的写法是直接调用并捕获异常:
try: data.append(item) except AttributeError: raise TypeError("object does not support append") - 注意:不要滥用
hasattr(),它可能触发属性访问副作用;生产环境推荐 EAFP(Easier to Ask for Forgiveness than Permission)原则
用 typing.Protocol 给鸭子类型加一层可读性保障
纯鸭子类型难追溯、IDE 不提示、协作时易出错。Python 3.8+ 的 Protocol 可以在不破坏动态性的前提下,显式声明“需要哪些方法”:
立即学习“Python免费学习笔记(深入)”;
from typing import Protocol <p>class Drawable(Protocol): def draw(self) -> None: ...</p><p>def render(obj: Drawable) -> None: obj.draw() # IDE 知道 obj 必须有 draw 方法
-
Drawable不是父类,任何含draw()方法的类/实例都能传给render() - 类型检查器(如 mypy)会验证,但运行时不强制——仍保留鸭子类型的灵活性
- 避免写
Union[Circle, Rect, SVG]这种枚举式类型注解,协议更开放
真正容易被忽略的是边界:鸭子类型不解决方法语义一致性问题。两个对象都有 save(),一个存数据库一个写文件,仅靠名字无法区分——这时候得靠文档、命名约定或协议注解来补足意图表达。

















