ABC 的核心价值是提前暴露错误并支撑类型检查、IDE 补全与协作,而非阻止运行;应优先显式继承而非 register();@property 与 @abstractmethod 共用时需注意装饰器顺序;ABC 与 Protocol 混用易致类型检查矛盾,且无法约束隐式行为契约。

为什么 ABC 不只是“写个 raise NotImplementedError”
因为光抛异常不能约束子类实现,也不能在运行时识别接口契约。ABC 的核心价值是让类型检查、IDE 补全、单元测试和团队协作有据可依。它不是为了阻止代码运行,而是为了让「错在哪」更早暴露——比如 isinstance(obj, MyProtocol) 返回 False,比等到调用 obj.process() 才报 AttributeError 更利于定位。
register() 和继承的取舍:什么时候该用哪个
动态注册(MyABC.register(ConcreteClass))适合无法修改源码的第三方类,或需延迟绑定的插件系统;但会绕过静态类型检查器(如 mypy),且 issubclass(ConcreteClass, MyABC) 仍为 False。真要建模“是某种东西”,优先用显式继承:class Worker(Executor): ...。否则容易出现 mypy 认为不兼容、而运行时又意外通过的隐晦问题。
带 @abstractmethod 的属性和 @property 怎么共存
直接在抽象基类里写 @property + @abstractmethod 是合法的,但要注意顺序:@abstractmethod 必须紧贴方法定义,@property 在外层或内层都行,推荐外层写法:
class CacheBackend(ABC):
@property
@abstractmethod
def capacity(self) -> int:
...
否则子类重写时可能漏掉 @property 装饰,导致属性变方法,调用方式突变(cache.capacity 变成 cache.capacity())。
立即学习“Python免费学习笔记(深入)”;
和 Protocol 混用时的实际风险
Protocol 是结构化协议,ABC 是名义化契约,两者语义不同。若一个类同时满足某个 Protocol 和继承自某 ABC,mypy 可能因路径不同给出矛盾提示。更常见的是:有人用 ABC 定义接口,却在函数签名里注解 Protocol,结果子类实例传入时报错——因为 isinstance(x, MyProtocol) 为 True,但 isinstance(x, MyABC) 未必成立,而运行时逻辑依赖后者判断。
真正难处理的,是那些靠文档约定行为、却没被任何类型机制捕获的隐式契约。ABC 能管住方法名和签名,管不住“调用 close() 后再调 read() 必须抛 ValueError”这类规则。










