什么是鸭子类型(duck typing)?
定义
鸭子类型是一种动态类型风格,判断一个对象能否被使用,不看它的类型,而看它是否具有所需的方法或属性。
"如果它走起路来像鸭子,叫起来也像鸭子,那它就是鸭子。"
Python 不要求显式声明接口或继承关系,只要对象有对应的方法,就可以直接使用。
基础示例
class Duck:
def quack(self):
print("嘎嘎嘎")
class Person:
def quack(self):
print("我在模仿鸭子叫")
class Cat:
def meow(self):
print("喵喵喵")
def make_it_quack(obj):
obj.quack() # 不关心 obj 是什么类型,只要有 quack 方法
make_it_quack(Duck()) # 嘎嘎嘎
make_it_quack(Person()) # 我在模仿鸭子叫
make_it_quack(Cat()) # ❌ AttributeError: 'Cat' object has no attribute 'quack'
Duck 和 Person 没有任何继承关系,但都能传入 make_it_quack。
对比:静态类型 vs 鸭子类型
# Java 风格(需要显式接口)
# interface Quackable { void quack(); }
# class Duck implements Quackable { ... }
# Python 风格(鸭子类型,无需接口声明)
class Duck:
def quack(self): ...
class Robot:
def quack(self): print("机器鸭叫声")
# 只要有 quack 方法,都能用
for obj in [Duck(), Robot()]:
obj.quack()
Python 中鸭子类型的体现
1. 内置协议
Python 的许多内置功能依赖特殊方法,而非具体类型:
class MyList:
def __init__(self, data):
self.data = data
def __len__(self):
return len(self.data)
def __getitem__(self, index):
return self.data[index]
def __iter__(self):
return iter(self.data)
obj = MyList([1, 2, 3])
print(len(obj)) # 3 ← 有 __len__ 就能用 len()
for x in obj:
print(x) # 1 2 3 ← 有 __iter__ 就能 for 循环
print(obj[0]) # 1 ← 有 __getitem__ 就能下标访问
2. 文件类对象
import io
def process(file_like):
return file_like.read() # 只要有 read() 方法即可
# 真实文件
with open("demo.txt") as f:
process(f)
# 内存中的字符串流
process(io.StringIO("hello world"))
# 自定义对象
class FakeFile:
def read(self):
return "fake content"
process(FakeFile())
3. 可迭代对象
def sum_all(iterable):
total = 0
for x in iterable:
total += x
return total
print(sum_all([1, 2, 3])) # 列表
print(sum_all((1, 2, 3))) # 元组
print(sum_all({1, 2, 3})) # 集合
print(sum_all(x for x in range(4))) # 生成器
EAFP 编程风格
鸭子类型鼓励 EAFP(Easier to Ask Forgiveness than Permission,先用再处理异常),而非 LBYL(Look Before You Leap,用前先检查类型):
# LBYL(不推荐)
if hasattr(obj, "quack"):
obj.quack()
# EAFP(Python 推荐风格)
try:
obj.quack()
except AttributeError:
print("该对象不会叫")
鸭子类型 vs 抽象基类(ABC)
鸭子类型足够灵活,但有时需要明确约束。Python 提供抽象基类作为补充:
from abc import ABC, abstractmethod
class Quackable(ABC):
@abstractmethod
def quack(self): ...
class Duck(Quackable):
def quack(self):
print("嘎嘎嘎")
class BadDuck(Quackable):
pass # ❌ 未实现 quack,实例化时报 TypeError
- 鸭子类型:运行时检查,灵活,适合快速开发
- 抽象基类:提前约束接口,适合团队协作和大型项目
总结
- 鸭子类型的核心:关注行为,而非类型
- Python 大量内置机制(
for、len、with、运算符重载)都基于鸭子类型 - 优点:灵活、解耦,无需复杂继承体系
- 缺点:错误在运行时才暴露,可结合类型提示(
typing)和抽象基类(ABC)弥补