Python 没有 interface 关键字,「能力契约」有三条表达路径:运行时鸭子类型、静态
typing.Protocol、名义化abc.ABC。本文拆解三者的机制边界与选型。
鸭子类型与 EAFP
Python 的类型判定只看行为不看血统——走起来像鸭子、叫起来像鸭子,那就是鸭子。函数拿到参数直接调用其方法(判定依据正是 特殊方法 那套协议),不检查 isinstance;配套 EAFP 风格(Easier to Ask Forgiveness than Permission):先直接调用,错了再捕获。
def total(obj):
return sum(obj) # 不查类型,可迭代就行
total([1, 2, 3]) # list
total(range(10)) # range
total(x for x in range(3)) # 生成器,见[[生成器]]灵活的代价:契约只存在于文档和调用点,方法名拼错要到运行时才炸,静态检查工具无从插手。协议与抽象基类都是给这份隐式契约补文档、补检查的手段。
静态协议:typing.Protocol
typing.Protocol 实现结构化类型(structural typing):类只要拥有协议声明的全部方法签名,就算实现了协议——不继承、不注册,检查交给 mypy / pyright 等静态检查器。注解背景见 类型注解 typing。
from typing import Protocol
class Closable(Protocol):
def close(self) -> None: ...
def process(res: Closable) -> None:
res.close() # 检查器只关心传入对象有没有close
class Conn: # 没继承Closable
def close(self) -> None: ...
process(Conn()) # 结构匹配,静态检查通过@runtime_checkable 让协议支持 isinstance,但只验证方法存在,不验证签名:
from typing import Protocol, runtime_checkable
@runtime_checkable
class Closable(Protocol):
def close(self) -> None: ...
isinstance(Conn(), Closable) # True,仅查属性存在性协议方法带默认实现时,显式继承协议的类直接复用;仅靠结构匹配的类则要自己写全。
抽象基类:abc.ABC
abc.ABC 走名义化类型(nominal typing):子类必须显式继承,@abstractmethod 标记必须实现的方法。抽象方法没实现完就实例化,直接 TypeError——把「漏实现」从运行时调用点提前到构造点:
from abc import ABC, abstractmethod
class Storage(ABC):
@abstractmethod
def save(self, key: str, value: str) -> None: ...
@abstractmethod
def load(self, key: str) -> str: ...
class MemStorage(Storage):
def __init__(self):
self._d = {}
def save(self, key, value):
self._d[key] = value
def load(self, key):
return self._d[key]
Storage() # TypeError:抽象方法未实现,无法实例化
MemStorage() # 正常抽象基类还能携带具体方法搭模板方法骨架:父类定流程、子类填细节,方法解析配合 继承与 MRO。
虚拟子类:register 与 subclasshook
ABC 支持事后声明继承关系。register() 把已有类注册为虚拟子类——不动类源码,isinstance 立即认亲(但不注入任何方法,也不检查是否真的实现了接口):
Storage.register(dict)
isinstance({}, Storage) # True__subclasshook__ 把子类判定写成任意逻辑,让抽象基类获得结构化判定能力——collections.abc 正是用它实现「有 __iter__ 即 Iterable 子类」:
@classmethod
def __subclasshook__(cls, sub):
if cls is Storage and hasattr(sub, "save") and hasattr(sub, "load"):
return True # 有这两个方法就算子类
return NotImplemented # 交回默认判定collections.abc 体系
标准库用抽象基类给容器能力画出层次:Iterable → Iterator,Sized / Container → Collection → Sequence / Mapping / Set。注解容器参数优先用它们而非具体类型——Sequence 同时接受 list 与 tuple。常用容器见 collections。
选型:Protocol vs ABC
| 维度 | typing.Protocol | abc.ABC |
|---|---|---|
| 类型体系 | 结构化:有方法即匹配 | 名义化:必须继承或 register |
| 检查时机 | 静态检查器为主,runtime_checkable 可运行时 | 运行时(实例化、isinstance) |
| 强制力 | 默认不进运行时 | 未实现抽象方法无法实例化 |
| 适用 | 第三方类型不便继承、零侵入声明能力 | 框架骨架、模板方法、显式继承体系 |
直觉:给已有类型补契约、只想让 mypy 满意 → Protocol;要搭继承骨架、要在运行时强制实现 → ABC。两者都是给鸭子类型划定边界,与运行时的隐式查找路径互为补充。