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.Protocolabc.ABC
类型体系结构化:有方法即匹配名义化:必须继承或 register
检查时机静态检查器为主,runtime_checkable 可运行时运行时(实例化、isinstance)
强制力默认不进运行时未实现抽象方法无法实例化
适用第三方类型不便继承、零侵入声明能力框架骨架、模板方法、显式继承体系

直觉:给已有类型补契约、只想让 mypy 满意 → Protocol;要搭继承骨架、要在运行时强制实现 → ABC。两者都是给鸭子类型划定边界,与运行时的隐式查找路径互为补充。