Python: Callable、Protocol、ABCMETA+abstractmethod对比


这是一个非常经典的问题,涉及 Python 中三种定义接口或行为规范的方式:

  • Callable:用于标注函数/可调用对象类型。
  • Protocol:结构化类型接口,支持鸭子类型和灵活的行为描述。
  • ABCMeta + @abstractmethod:正式的抽象基类机制,强调显式继承和实现检查。


🧩 总览对比

特性CallableProtocolABCMeta + @abstractmethod
定义用途指定函数签名类型定义接口规范(属性、方法等)定义抽象类,强制子类实现方法
是否强制继承❌(结构化)❌(结构化)✅ 必须显式继承
是否支持属性
是否支持多方法接口
运行时类型检查(isinstance✅(@runtime_checkable)支持
面向鸭子类型❌(基于继承)
是否支持抽象类验证✅(子类必须实现所有抽象方法)
使用版本要求Python 3.5+Python 3.8+(或 3.7 via backport)Python 2.7+(推荐 Python 3+)


🎯 用途场景对比

场景推荐方式
需要声明一个函数类型(如:回调、策略)Callable
需要接收“函数”或“实现了 call 的类”Protocol
希望函数对象还包含 name/version 等属性Protocol
希望显式约束类必须实现某些方法ABCMeta + @abstractmethod
插件系统、策略模式,结构接口不强制继承Protocol
框架设计中需要强制子类实现完整接口ABCMeta


🔬 实例对比

我们来设计一个“打折策略”的例子:



Callable 示例(函数签名)

from typing import Callable
from decimal import Decimal

class Order:
    total = Decimal('100.0')

DiscountStrategy = Callable[[Order], Decimal]

def ten_percent(order: Order) -> Decimal:
    return order.total * Decimal('0.1')

📌 只能表示函数,不能拥有属性、多个方法



Protocol 示例(鸭子类型 + call

from typing import Protocol, runtime_checkable

class Order:
    total = 100.0

@runtime_checkable
class DiscountStrategy(Protocol):
    def __call__(self, order: Order) -> float: ...
    name: str

class TenPercent:
    name = "10% off"
    def __call__(self, order: Order) -> float:
        return order.total * 0.1

promo = TenPercent()
assert isinstance(promo, DiscountStrategy)  # ✅ 运行时检查

📌 可扩展:支持多个方法、属性,可用于类或函数。



ABCMeta + abstractmethod 示例(强制继承)

from abc import ABC, abstractmethod

class DiscountStrategy(ABC):
    @abstractmethod
    def apply(self, order: Order) -> float:
        pass

class TenPercent(DiscountStrategy):
    def apply(self, order: Order) -> float:
        return order.total * 0.1

# TenPercent 不实现 apply 会报错:Can't instantiate abstract class

📌 继承结构清晰,适合框架内部强约束接口。



✅ 总结:三者何时使用?

你想做的事情选择方法
快速传一个函数进来作为参数Callable
希望支持函数或类对象,且具备结构性Protocol
接口中包含属性/方法组合,面向行为结构Protocol
定义正式抽象接口,强制所有子类实现ABC + @abstractmethod
需要 isinstance(obj, Interface) 的语义支持Protocol with @runtime_checkable
希望写插件框架,接收各种灵活对象Protocol


✅ 最终建议

| 简洁函数签名        | 用 Callable              |

| 结构化可调用对象     | 用 Protocol + __call__   |

| 接口+属性/行为组合   | 用 Protocol              |

| 面向继承强约束       | 用 ABCMeta + @abstractmethod |



如果你告诉我你的具体使用场景(如策略模式、插件系统、验证接口、异步任务等),我可以给出更具体的推荐和模板。需要吗?

文章标签:

评论(0)