Python 多重继承与 MRO
说明 Python 多重继承为什么存在,MRO 如何决定属性查找顺序,以及 super 如何沿 C3 线性化后的顺序协作。
#type / concept
#status / growing
#resource / python
#tech / lang / python
[!info] related notes
- 所属 MOC: Python 类与面向对象 MOC
- 前置概念: python继承方法重写与super
- 并列概念: python特殊方法
- 易混淆概念: 鸭子类型
- 关系笔记: python类与面向对象
Python 多重继承与 MRO
一句话定义
多重继承允许一个类同时继承多个父类,而 MRO(Method Resolution Order,方法解析顺序)负责把“应该先找谁、再找谁”线性化成一条稳定顺序。
核心机制 / 工作原理
1. 多重继承解决的是“组合多种正交能力”
Python 允许:
class Child(Base1, Base2):
...
这意味着 Child 既能复用 Base1,也能复用 Base2。最常见的合理用途不是把多个“大而全的业务父类”乱堆在一起,而是:
- 一个主基类提供核心语义
- 若干 mixin 提供正交能力,例如日志、序列化、缓存、权限检查
2. 问题不在“能继承几个”,而在“查找顺序怎么定”
当 Child、Base1、Base2 里都定义了同名方法时,Python 必须回答:
- 先调用哪一个
- 下一个该轮到谁
- 菱形继承里公共祖先是否会被重复执行
这就是 MRO 要解决的问题。
3. Python 用 C3 线性化生成 MRO
你可以先记住 C3 的三个目标:
- 保留子类声明的局部顺序,例如
Child(Base1, Base2)表示Base1优先于Base2 - 保留每个父类内部原有的继承顺序
- 同一个类在线性结果里只出现一次
最终 Python 会得到一条形如:
Child -> Base1 -> Base2 -> CommonBase -> object
这样的线性链,属性查找和 super() 都沿这条链工作。
4. super() 在多重继承里依赖这条线性链协作
只要每个类都调用 super(),公共祖先就能按 MRO 恰好执行一次,而不会因为菱形结构被重复调用。
最小例子 / 最小场景
class A:
def ping(self):
print("A")
class B(A):
def ping(self):
print("B start")
super().ping()
print("B end")
class C(A):
def ping(self):
print("C start")
super().ping()
print("C end")
class D(B, C):
pass
D().ping()
print(D.__mro__)
输出顺序会体现协作链:
B start
C start
A
C end
B end
因为 D 的 MRO 大致是 D -> B -> C -> A -> object,所以 super() 不是“回父类”,而是“沿 MRO 继续向后走”。
边界与易混淆点
- 多重继承不是默认首选;如果只是把两个对象拼起来协作,组合通常更简单。
- 真正适合多重继承的是 mixin 风格的“可叠加能力”,而不是多个互相强耦合的具体父类。
- 只要某个类跳过
super(),整条协作链就可能断掉;这也是为什么直接写死Base.method(self)很危险。