应选抽象基类(ABC)实现运行时强制接口契约,协议(Protocol)适用于静态类型检查的结构化契约;二者在继承机制、检查时机、类型验证方式及组合语法上存在根本差异。

如果您在 Python 中需要定义一种接口规范,但又不确定该选择抽象基类(ABC)还是协议(Protocol),则可能源于二者在语法表现和运行时行为上的关键差异。以下是理解二者边界的具体路径:
一、抽象基类(ABC)的强制性契约
抽象基类通过继承机制建立显式的类型契约,子类必须实现所有标记为 @abstractmethod 的方法,否则实例化时将抛出 TypeError。这种检查发生在运行时,且依赖于 abc.ABC 基类和装饰器。
1、导入 abc 模块并定义一个继承自 ABC 的类。
2、在类中使用 @abstractmethod 装饰器标注至少一个方法。
立即学习“Python免费学习笔记(深入)”;
3、尝试直接实例化该类,将触发 TypeError: Can't instantiate abstract class。
4、定义子类并继承该 ABC,然后在子类中实现全部抽象方法。
5、子类实例化成功,且可通过 isinstance(obj, ABC) 进行类型检查。
二、协议(Protocol)的结构性契约
协议采用鸭子类型思想,不依赖继承关系,仅要求对象具备指定名称和签名的属性或方法。类型检查器(如 mypy)在静态分析阶段验证协议符合性,而解释器在运行时不执行任何强制检查。
1、从 typing 模块导入 Protocol。
2、定义一个类并继承 Protocol,在其内部仅声明方法签名,不加 @abstractmethod。
3、编写一个普通类,不继承该 Protocol,但实现其全部方法签名。
4、mypy 会接受该普通类作为该 Protocol 的子类型,即使无继承关系。
5、运行时调用该普通类实例的方法不会触发协议相关异常。
三、运行时类型检查的适用场景区分
当需要在程序运行过程中动态判断对象是否满足某组接口能力时,应使用 ABC 配合 isinstance() 或 issubclass();而 Protocol 无法参与此类运行时检查,isinstance(obj, SomeProtocol) 总是返回 False(除非显式注册)。
1、对一个已知实现 ABC 的实例调用 isinstance(instance, MyABC),返回 True。
安全 Python 代码沙箱:在隔离的沙箱中执行任意 Python 代码,预装 requests、NumPy、pandas 库。返回 stdout、stderr。
2、对一个仅满足 Protocol 结构的实例调用相同检查,返回 False。
3、若需让 Protocol 支持运行时检查,可调用 SomeProtocol.register(ConcreteClass) 显式注册。
4、注册后,isinstance(instance, SomeProtocol) 返回 True。
5、注册不改变类定义,也不触发方法存在性验证。
四、多重协议组合与 ABC 继承的语法差异
Protocol 支持使用 Union 和 +(连接符) 组合多个协议,形成新的结构要求;而 ABC 仅支持单继承或多继承语法,且多继承时若存在同名抽象方法,必须在子类中明确实现。
1、定义 ProtocolA 和 ProtocolB,各自包含不同方法签名。
2、使用 class SupportsAAndB(ProtocolA, ProtocolB): pass 构建组合协议。
3、定义一个类,同时实现 ProtocolA 和 ProtocolB 的全部方法。
4、该类被 mypy 视为 SupportsAAndB 的有效实现。
5、若用 ABC 实现类似效果,需定义 BaseABC,再令 ConcreteABC 同时继承多个 ABC,并逐一实现所有抽象方法。
五、__init_subclass__ 与 __init__ 在协议和 ABC 中的行为差异
ABC 允许在抽象基类中定义 __init_subclass__ 方法,用于拦截子类创建过程,执行注册或验证逻辑;而 Protocol 类中定义的 __init_subclass__ 不会被调用,因其不参与真正的继承链构建。
1、在 ABC 类中定义 def __init_subclass__(cls, **kwargs): 并加入打印语句。
2、创建继承该 ABC 的子类,观察打印输出被执行。
3、在 Protocol 类中做同样定义。
4、创建结构匹配的普通类,不继承该 Protocol,打印语句不执行。
5、即使对该 Protocol 调用 register(),__init_subclass__ 仍不会触发。

















