Python 的 MRO 是什么?super() 在多继承中如何工作?
简化版
MRO 是方法解析顺序,决定 Python 在继承层级中查找属性和方法的顺序。Python 3 使用 C3 线性化生成 MRO;super() 不是简单调用父类,而是沿着当前类的 MRO 调用下一个类的方法。
详细版
示例:
class A:
def run(self):
print("A")
class B(A):
def run(self):
print("B")
super().run()
class C(A):
def run(self):
print("C")
super().run()
class D(B, C):
def run(self):
print("D")
super().run()
d = D()
d.run()
print(D.__mro__)
输出顺序:
D
B
C
A
D.__mro__ 类似:
(D, B, C, A, object)
所以 super() 会沿 MRO 找下一个类,而不是固定找语法上的直接父类。多继承中要让链条完整,相关类的方法签名要兼容,并且都要正确调用 super()。
完整版教学
一、为什么需要 MRO
单继承时,方法查找很直观:
class B(A):
pass
查找 B 的方法时,先找 B,再找 A。
多继承就复杂了:
class D(B, C):
pass
如果 B 和 C 都继承自 A,查找顺序怎么排?A 会不会被调用两次?父类之间的相对顺序如何保持?
MRO 就是为了解决这些问题。
二、查看 MRO 的方式
可以用:
print(D.__mro__)
或者:
print(D.mro())
输出是一个类序列。Python 查找属性和方法时,就按这个序列从左到右找。
例如:
class A:
value = "A"
class B(A):
value = "B"
class C(A):
value = "C"
class D(B, C):
pass
print(D.value) # B
因为 MRO 是 D -> B -> C -> A -> object,先在 B 找到了 value。
三、C3 线性化要保证什么
Python 3 的 MRO 使用 C3 线性化。面试里不一定要手算复杂 MRO,但要知道它遵守几个原则:
- 子类在父类前面;
- 多继承列表中,左边父类优先于右边父类;
- 如果多个父类共享同一个祖先,祖先只出现一次;
- 保持继承关系的一致性,无法一致时会报错。
例如:
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
MRO 是:
D, B, C, A, object
A 不会因为 B 和 C 都继承它就出现两次。
四、super() 不是“父类名”的替代
很多人把:
super().run()
理解成:
Parent.run(self)
这在单继承里看起来差不多,但多继承里会出错。
在 D(B, C) 中,B 里的 super().run() 调到的不是 A.run,而是 MRO 中 B 后面的 C.run。
D -> B -> C -> A -> object
所以 super() 的真实含义是:从当前类在 MRO 中的位置开始,找下一个类。
五、协作式多继承
多继承中,如果每个类都想参与同一条调用链,就要使用协作式写法:
class A:
def __init__(self, **kwargs):
super().__init__(**kwargs)
class B(A):
def __init__(self, b=None, **kwargs):
self.b = b
super().__init__(**kwargs)
class C(A):
def __init__(self, c=None, **kwargs):
self.c = c
super().__init__(**kwargs)
class D(B, C):
def __init__(self, **kwargs):
super().__init__(**kwargs)
这里每一层都把自己处理不了的参数继续往后传。这样 MRO 链才能完整执行。
六、什么时候避免多继承
多继承很强,但也容易复杂。
适合多继承的场景:
- mixin 小能力组合;
- 框架规定的扩展点;
- 多个横向能力叠加。
不适合:
- 多个父类都有复杂状态;
- 父类初始化参数不兼容;
- 继承层级很深;
- 只是为了复用几行代码。
如果只是复用能力,组合通常更容易维护。
七、常见误区与追问
心法:
super()不是“调用父类”,而是“沿着当前类的 MRO 链找下一个实现”。多继承能不能稳,关键看每一层是否协作式调用。
- 误区:
super()永远等价于直接写父类名。 直接写父类名会固定调用目标,可能跳过 MRO 链上的其他类;super()会根据 MRO 找下一个类,更适合协作式多继承。 - 追问:C3 线性化解决什么问题? 它把多继承图变成一个稳定线性顺序,同时尽量保持局部继承顺序和单调性,避免同一个祖先类被重复或混乱调用。
- 误区:菱形继承一定会重复调用顶层父类。 如果所有类都正确使用
super(),Python 会沿 MRO 让每个类按顺序出现一次;手写父类名才更容易重复调用。 - 追问:为什么 mixin 通常不要有复杂状态? mixin 适合提供小而独立的横向能力;如果它依赖复杂初始化参数,多继承链里的参数传递和初始化顺序会变脆。
- 误区:只要能跑,多继承就没问题。 多继承的维护风险在后续扩展时暴露,尤其是父类参数不兼容、方法同名、状态初始化互相依赖时。
- 追问:怎么调试 MRO? 用
Class.__mro__或Class.mro()打印顺序,再检查每个类的方法是否调用了super()并正确透传参数。
八、加强记忆
MRO 决定方法查找顺序,Python 3 用 C3 线性化保证顺序一致;super() 沿 MRO 找下一个类,不是机械地找“亲爹”。多继承里要让每个类都配合 super(),否则调用链可能断掉。面试讲 MRO 时,最好配一个菱形继承例子。