抽象基类必须显式继承abc,否则@abstractmethod无效;__subclasshook__仅影响isinstance检查,不触发抽象方法校验;抽象属性需@abstractmethod包裹@property;接口设计应预留扩展点避免破坏性变更。

抽象基类必须显式继承 ABC,否则 @abstractmethod 不生效
很多人写完 @abstractmethod 却发现子类不实现它也不报错,根本原因是没让类继承 ABC。Python 的抽象机制不是靠装饰器单独驱动的,而是依赖 ABC 的元类(ABCMeta)在类创建时检查。如果只加装饰器不继承,那只是个普通方法,运行时完全不会强制约束。
- 正确写法:
class Shape(ABC):—— 必须显式继承 - 错误写法:
class Shape:+@abstractmethod—— 子类可随意忽略,无任何检查 - 注意:从 Python 3.4 起,
ABC在abc模块中,需from abc import ABC, abstractmethod
__subclasshook__ 是鸭子类型与结构协议的桥梁,但不能替代真正继承
当不想强制子类显式继承抽象基类,又希望 isinstance(obj, MyInterface) 返回 True 时,__subclasshook__ 就派上用场。但它只影响类型检查(如 isinstance、issubclass),**不触发 @abstractmethod 的运行时校验**,也不参与 MRO 查找。
- 典型用途:让内置类型(如
list、dict)或第三方类“假装”实现了你的接口 - 返回
NotImplemented表示不接管判断,交由默认逻辑;返回True或False才生效 - 示例:
if hasattr(cls, 'read') and hasattr(cls, 'write'): return True - 风险:容易写出过于宽松的钩子,导致
isinstance判定为真,但实际调用.read()时才抛AttributeError
抽象方法 + 抽象属性混用时,@property 和 @abstractmethod 的顺序不能错
定义抽象属性时,必须把 @abstractmethod 放在最外层,否则装饰器链失效,子类可以不实现该属性,且不会报错。
- ✅ 正确:
@property→@abstractmethod(即@abstractmethod包裹@property) - ❌ 错误:
@property→@abstractmethod反过来,或两者并列——Python 会忽略抽象约束 - 等价写法:
@abstractproperty(已弃用,3.10+ 移除,必须用组合方式) - 同理适用于
@classmethod、@staticmethod:@abstractmethod必须是最外层装饰器
大型项目中,抽象基类不该暴露具体实现细节,但要预留扩展点
接口一旦发布,修改抽象方法签名(比如加参数、改返回类型)就是破坏性变更。与其后期打补丁,不如早期设计时就用 **kwargs 或可选参数留余地,或拆出 _do_something_v2 这类受保护的钩子方法供子类选择性覆盖。
- 避免:
def process(self, data, timeout)→ 后来想加format参数,就得改所有子类 - 推荐:
def process(self, data, **options),并在文档里说明timeout和format是支持的 key - 更稳妥:
def process(self, data):+def _process_impl(self, data, **options):(后者设为@abstractmethod) - 注意:
ABC本身不提供版本控制,团队需配合 mypy 类型注解 + CI 检查来守住契约
抽象基类真正的难点不在语法,而在接口边界的划分——哪些必须统一,哪些应该放开。写得太严,子类寸步难行;写得太松,等于没约束。每次加一个 @abstractmethod 前,先问一句:这个行为是否真的跨所有实现都不可省略?
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











