背景
Duck typing 是 Python 的一种类型哲学:不关心对象的类型名,只关心对象有没有需要的方法/属性。名字来自"如果一个东西走起来像鸭子、叫起来像鸭子,那它就是鸭子"。这种"只看行为不看血统"的写法一开始容易让人困惑,尤其是从 C++/Swift 这类强类型语言过来时。
核心示例
class Duck:
def quack(self): print("嘎嘎")
class Person:
def quack(self): print("我在模仿鸭子")
def make_it_quack(thing):
thing.quack() # 不关心 thing 的类型,只要有 quack() 方法就行
Duck 和 Person 完全没有继承关系,make_it_quack 也不做任何类型检查,运行到 thing.quack() 时才会去看这个方法存不存在。
优势:别的语言能不能这样写?
能,但通常需要提前显式声明继承/遵循关系。
C++:必须有公共基类
class Quackable {
public:
virtual void quack() = 0;
};
class Duck : public Quackable {
public:
void quack() override { cout << "嘎嘎"; }
};
class Person : public Quackable {
public:
void quack() override { cout << "我在模仿鸭子"; }
};
void make_it_quack(Quackable& thing) {
thing.quack();
}
Swift:必须显式遵循 Protocol
protocol Quackable {
func quack()
}
class Duck: Quackable {
func quack() { print("嘎嘎") }
}
class Person: Quackable {
func quack() { print("我在模仿鸭子") }
}
func makeItQuack(_ thing: Quackable) {
thing.quack()
}
关键场景:如果 Duck 和 Person 是第三方库里定义的类,你根本没法修改它们的源码去加继承关系。C++/Swift 遇到这种情况通常得写适配器(wrapper)包一层;Python 则完全不用做任何声明,直接传对象进去就行,因为检查发生在调用那一刻,不关心"血统"。
这也是 duck typing 最大的实际价值:解耦。不需要强迫毫不相关的类共享一个基类才能被同一个函数使用。
注意事项
纯运行时 duck typing 也有真实的代价:
- 没有编译期保证:传错对象时,程序要跑到调用
.quack()那一行才会报AttributeError,而不是在传参那一刻就报错。 - 隐式契约难维护:函数签名看不出"到底需要对象具备哪些方法",必须读函数体才知道。
折中方案:结构化类型 + 静态检查
现代实践更倾向于"声明形状,但不强制继承",把 duck typing 的灵活性和静态类型的安全性结合起来:
Python
typing.Protocol:声明一个"形状",Duck类完全不用改,只要它恰好有quack()方法,类型检查器(mypy/pyright)就认为它满足Quackable,且能在写代码时就检查出错误,而不是等运行时炸。from typing import Protocol class Quackable(Protocol): def quack(self) -> None: ... def make_it_quack(thing: Quackable) -> None: thing.quack()TypeScript:类型系统本身就是结构化类型(structural typing),
interface匹配看的是形状而不是名字——相当于"静态检查版的 duck typing",不需要显式写implements。Swift:
Protocol更接近显式声明遵循关系(必须写: Quackable),但本质上也是行为导向而非继承导向。
小结
| 语言 | 类型判断方式 | 是否需要显式声明 | 检查时机 |
|---|---|---|---|
| Python(纯 duck typing) | 看方法是否存在 | 不需要 | 运行时 |
Python(typing.Protocol) | 看方法是否存在 | 不需要(但可加类型检查) | 静态检查 + 运行时 |
| C++ | 看继承关系 | 需要(public Quackable) | 编译期 |
| Swift | 看是否遵循 Protocol | 需要(: Quackable) | 编译期 |
| TypeScript | 看结构是否匹配 | 不需要 | 编译期(结构化类型) |
