鸭子类型
"If it walks like a duck and quacks like a duck, it's a duck."
Python 不在乎对象的类型,只在乎它有没有需要的方法:
def make_quack(duck):
duck.quack()
class RealDuck:
def quack(self): print("Quack!")
class FakeDuck:
def quack(self): print("噶噶噶")
make_quack(RealDuck()) # OK
make_quack(FakeDuck()) # 也 OK
ABC:明确规定"必须实现哪些方法"
abstractmethod 装饰的方法子类必须实现,否则不能实例化:
from abc import ABC, abstractmethod
class Storage(ABC):
@abstractmethod
def save(self, key, value): pass
@abstractmethod
def load(self, key): pass
class MemoryStorage(Storage):
def __init__(self):
self.data = {}
def save(self, key, value):
self.data[key] = value
def load(self, key):
return self.data.get(key)
# class IncompleteStorage(Storage):
# def save(self, k, v): self.x = v
# IncompleteStorage() # TypeError: 必须实现 load
ABC 的好处
- 早报错:忘了实现某方法,实例化时就报错,不用等到运行时
- 文档作用:看一眼 ABC 就知道"这一族"对象的接口
- isinstance 检查:
isinstance(s, Storage)能查所有子类
Protocol:结构化子类型(PEP 544)
ABC 要显式继承才生效。Protocol 不需要——只要"长得像":
from typing import Protocol
class Quackable(Protocol):
def quack(self) -> None: ...
class Duck:
def quack(self):
print("Quack!")
def make_quack(d: Quackable): # 类型注解
d.quack()
make_quack(Duck()) # OK,Duck 没继承 Quackable,但符合协议
mypy / pyright 会根据"长得像不像"来判断,不需要继承。这就是 PEP 544 的"结构化子类型"。
何时用 ABC,何时用 Protocol
| 场景 | 用 |
|---|---|
| 内部代码、明确知道实现者 | ABC |
| 鸭子类型 + 静态类型检查 | Protocol |
| 第三方库不能改它 | Protocol(不需要它继承你的类) |
| 想用 isinstance 运行时检查 | ABC |
| Python 3.7 及以下 | ABC(Protocol 是 3.8+) |
标准库里的 ABC 例子
collections.abc 模块有大量预定义 ABC:
from collections.abc import Iterable, Sized, Mapping
class MyCol:
def __iter__(self): yield 1
def __len__(self): return 1
isinstance(MyCol(), Iterable) # True
isinstance(MyCol(), Sized) # True
定义自己的容器时常常继承 MutableMapping / Sequence 等,免得每个魔术方法都自己写。
runtime_checkable Protocol
Protocol 默认是静态的(只供类型检查)。加 @runtime_checkable 后可以 isinstance:
from typing import Protocol, runtime_checkable
@runtime_checkable
class Closable(Protocol):
def close(self) -> None: ...
isinstance(open("f.txt"), Closable) # True
下一篇讲魔术方法 dunder——让自定义类长得像内置类型。