Python Duck Typing

Twitter card.jpeg
Published on
/
5 mins read
/
––– views

背景

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() 方法就行

DuckPerson 完全没有继承关系,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()
}

关键场景:如果 DuckPerson 是第三方库里定义的类,你根本没法修改它们的源码去加继承关系。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

  • SwiftProtocol 更接近显式声明遵循关系(必须写 : Quackable),但本质上也是行为导向而非继承导向。

小结

语言类型判断方式是否需要显式声明检查时机
Python(纯 duck typing)看方法是否存在不需要运行时
Python(typing.Protocol看方法是否存在不需要(但可加类型检查)静态检查 + 运行时
C++看继承关系需要(public Quackable编译期
Swift看是否遵循 Protocol需要(: Quackable编译期
TypeScript看结构是否匹配不需要编译期(结构化类型)
← Previous post考研回忆录