组合与继承是“has-a”和“is-a”的不同设计思路,python中无“组合继承”语法;组合解耦强、类型清晰、控制粒度细但有间接调用开销;继承仅适用于明确“是一种”且父类明确支持子类化的场景;优化组合性能需预绑定属性、慎用__getattribute__、缓存方法绑定;仅当类专为继承设计且需被框架识别时才考虑继承。

组合和继承不是并列关系,而是两种不同设计思路:组合是“has-a”(拥有),继承是“is-a”(是一个)。所谓“组合继承”,其实是误称——Python中不存在一种叫“组合继承”的语法机制。你真正想问的,是组合与继承各自优缺点,以及如何在用组合时提升性能或降低开销。
组合的核心优势与潜在代价
组合通过在类中持有其他对象的引用来复用行为,不依赖类层级关系。
-
解耦性强:被组合的对象可随时替换(比如把
Path换成WindowsPath或 mock 对象),不影响外部接口 -
类型清晰:组合类是独立类型,不会意外被当成父类使用(避免
isinstance(obj, Path)返回True的混淆) - 控制粒度细:只暴露需要的方法,可过滤、修饰或拦截调用(如日志、权限检查)
-
但有间接调用开销:每次访问属性或方法都要经由
__getattr__或显式委托,比直接访问略慢;若频繁调用,可能成为瓶颈
继承的实际适用边界
继承适合表达明确的“是一种”关系,且满足严格前提:
- 父类文档明确支持子类化(如标注
intended for subclassing或继承自abc.ABC) - 你不重写核心协议方法(如
__new__、_flavour等底层逻辑) - 你完全掌控该子类的所有使用场景,不传给只认原始类型的第三方函数(如
shutil.copy(src, dst)只接受Path,不接受其子类) - 你愿意为每次 Python 版本升级做兼容性回归测试
现实中,满足全部条件的第三方类极少。像 dict、list 这类内置类型,更推荐用 UserDict、UserList 替代直接继承。
优化组合性能的关键做法
组合本身不慢,慢的是不当的代理方式。重点优化委托路径和属性访问:
-
避免无条件走
__getattr__:它只在属性未找到时触发,但每次都会引发一次失败查找。对高频访问的属性,应预先绑定到实例上(如self.exists = self._path.exists) -
用
__getattribute__谨慎替代__getattr__:若需统一拦截所有属性访问,可用它,但必须小心处理递归(比如访问self._path时不能再次触发自身) -
缓存委托对象的方法绑定:对不变的底层对象(如
self._path),可在__init__中预绑定常用方法:self.joinpath = self._path.joinpath,后续调用就是纯函数调用,无查找开销 -
对简单包装,考虑
__class__动态替换:若只是临时增强行为,可直接修改实例的__class__,绕过完整对象创建,但仅限内部可控场景
什么时候该放弃组合,改用继承?
极少情况。仅当以下四点同时成立:
- 你要扩展的类是专为继承设计的抽象基类(如
io.IOBase子类) - 你需要被框架自动识别为该类型(如作为参数传入
typing.BinaryIO注解的函数) - 你不需要替换底层实现,只追加行为(如新增
.backup()方法) - 你确认该类的 MRO 和协议方法(如
__iter__、__len__)在子类中仍能按预期工作
否则,坚持组合。它不炫技,但稳定、可测、易演进。











