Python 的 super() 到底在找谁?MRO(方法解析顺序)一次讲透
结论先行:super() 找的不是"父类",而是 MRO(方法解析顺序)链表里、位于当前类"之后"的那一个类。单继承时这两者恰好重合,所以用错了也没人发现;一旦上了多继承,两者立刻分道扬镳——你以为 super().hello() 在调父类的方法,它偏偏调了另一个分支。这篇用一个菱形继承例子,把 MRO 的算法和坑一次讲透。
1. 一个实验:super() 找到的不是你以为的父类
class A:
def hello(self):
print("A.hello")
class B(A):
def hello(self):
print("B.hello")
super().hello() # 这里你以为在调 A
class C(A):
def hello(self):
print("C.hello")
super().hello()
class D(B, C):
def hello(self):
print("D.hello")
super().hello()
D().hello()
print(D.__mro__)
D.hello
B.hello
C.hello
A.hello
(<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)
盯住 B.hello 里的 super().hello():B 的父类是 A,但它打印的是 C。 因为 D 的 MRO 是 D → B → C → A,B 的下一个是 C。super() 只认链表里的"下一个",不认继承图谱里的"父亲"。
2. 单继承为什么把这个误区藏了这么久
单继承时 MRO 就是一条直线,super() 的下一个类恰好就是父类,"找父类"这个错误心智模型永远能跑出正确结果。
class Parent:
def hello(self):
print("Parent.hello")
class Child(Parent):
def hello(self):
super().hello() # 单继承:下一个类 = 父类,模型蒙对
Child().hello() # Parent.hello
直到你写下第一个 class D(B, C),旧模型当场破产。 这也是为什么很多人写了几年 Python 都没撞见过这个问题。
3. 零参数 super() 的秘密:class 单元格
Python 3 里 super() 不传参数也能工作,靠的是编译器的帮忙:凡是方法里出现了 super(),编译器就往这个方法里塞一个 __class__ 闭包单元格,记下 super() 写在哪个类里。 运行时不光拿 self 的类型去 MRO 定位,还拿这个 __class__ 决定从哪开始找。
class A:
def hello(self):
print("super 写在这个类里:", super().__thisclass__)
print(A.hello.__code__.co_freevars) # ('__class__',)
A().hello()
('__class__',)
super 写在这个类里: <class '__main__.A'>
co_freevars 里的 ('__class__',) 就是证据。所以 super() 等价于 super(当前类, self)——第一个参数是 super() 所在的类,不是 self 的类。 这个区别在继承场景里会要命:B 继承自 A,B 的方法里写 super(),找的是 B 后面的类,而不是 A 后面的类。
4. MRO 是怎么算出来的:C3 线性化手推一遍
MRO 不是"深度优先"这种拍脑袋规则,是 C3 线性化算法的产物。三条军规:
- 子类永远排在父类前面;
- 各父类被指定的顺序要保留(
class D(B, C)里 B 就得在 C 前); - 一个类不能排在还在等它的类后面——如果某个列表的尾部还挂着一个类,这个类现在就不能被取出。
拿菱形开刀:D(B, C),B(A)、C(A)。已知 L(B) = [B, A, O]、L(C) = [C, A, O](O 是 object),算法就是不断从各列表的头部取类:
- 取 B:别的列表尾部没有 B,取出。剩余
[A,O]、[C,A,O]、[B,C]; - 头部是 A,但第二个列表的尾部
[A,O]里还有 A,跳过;换第二个列表的头部 C,无人等待,取出。剩余[A,O]、[A,O]、[]; - 取出 A,再取出 O。
最终 L(D) = [D, B, C, A, O],和实测一致:
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
print([c.__name__ for c in D.__mro__])
# ['D', 'B', 'C', 'A', 'object']
C3 的意义在于:A 在 MRO 里只出现一次——这就是"合作式多继承"的地基,下一节会看到它怎么保证基类只初始化一次。当然也有算不出结果的时候,Python 直接拒绝创建类:
class A: pass
class B: pass
class X(A, B): pass
class Y(B, A): pass
class Z(X, Y): pass
# TypeError: Cannot create a consistent method resolution order (MRO) for bases A, B
X 要求 A 在 B 前,Y 要求 B 在 A 前,Z 两个都想要——矛盾,C3 无解,解释器掀桌。
5. 多继承的头号坑:链式 super() 断在哪,初始化就丢到哪
class Base:
def __init__(self):
self.base = "base"
print("Base.__init__")
class Left(Base):
def __init__(self):
super().__init__()
self.left = "left"
print("Left.__init__")
class Right(Base):
def __init__(self):
# 忘了 super().__init__()
self.right = "right"
print("Right.__init__")
class Child(Left, Right):
def __init__(self):
super().__init__()
print("Child.__init__")
c = Child()
print(c.base) # AttributeError!
Right.__init__
Left.__init__
Child.__init__
AttributeError: 'Child' object has no attribute 'base'
MRO 是 Child → Left → Right → Base。调用链是:Child → super → Left → super → Right,Right 不调 super,链在这里断了,Base 根本没被初始化,c.base 直接 AttributeError。注意打印顺序是 Right 先、Left 后——链是深入到末端再回头往上冒的。
修起来只有一句话:每个 __init__ 都必须调 super().__init__(),一个都不能漏。 补上之后:
Base.__init__
Right.__init__
Left.__init__
Child.__init__
Base 只执行了一次,顺序是从最深层的基类往上冒泡,不是从上往下。C3 保证 Base 在 MRO 里唯一,所以合作式多继承不会重复初始化,也不会漏。
6. 和 Java 的心智模型对比
| 维度 | Java | Python |
|---|---|---|
| super 语义 | 明确指向直接父类 | 指向 MRO 里的下一个类 |
| 多继承 | 类不支持(接口无状态) | 支持,靠 MRO + C3 协调 |
| 漏调 super | 语言强制父类构造必执行 | 链静默断裂,后面全部不上班 |
| 重复初始化 | 结构上不可能 | 只要人人调 super,就不会 |
| 心智负担 | 低 | 多继承必须画 MRO |
把 Java 的"显式点名父类"搬进 Python 的菱形继承,会直接撞车:
class A:
def __init__(self):
print("A.__init__")
class B(A):
def __init__(self):
A.__init__(self) # Java 式显式点名
print("B.__init__")
class C(A):
def __init__(self):
A.__init__(self) # 又点一次名
print("C.__init__")
class D(B, C):
def __init__(self):
B.__init__(self)
C.__init__(self)
print("D.__init__")
D()
A.__init__
B.__init__
A.__init__
C.__init__
D.__init__
A 被初始化了两次,而且 D 想加新分支时还得手动改调用名单。Java 靠"单继承 + 强制"消灭了这个问题,Python 靠"线性化 + 合作"来解决它——自由是有票价的,票价就是每个作者都得守规矩。
7. 实战建议
- 单继承:随便用,
super().method()和老写法Parent.method(self)等价,但优先 super()——父类改名时不用跟着改。 - 多继承:先
print(Child.__mro__)画链路,再写代码;每个__init__都要super().__init__(**kwargs)把参数原样转发,否则参数走到一半就丢了。参数消费的标准姿势是"先转发、再消费":
class Base:
def __init__(self, **kw):
self.base = kw.pop("base", "?")
print("Base.__init__")
class Mixin(Base):
def __init__(self, **kw):
super().__init__(**kw)
self.extra = kw.pop("extra", "?")
class App(Mixin):
def __init__(self):
super().__init__(base="b", extra="e")
app = App()
print(app.base, app.extra) # b e
Base.__init__
b e
- mixin 里别假设 super() 的下一个类是谁——你写的时候,下一个类可能还没出生。
- 别用
super(类名, self)的老写法:零参数版本靠编译器注入__class__,在嵌套函数里也能正确工作;老写法遇到类套类定义,直接 NameError。
8. 一句话总结
super() 是 MRO 链表上的"下一个"指针,不是继承图谱上的"父亲"箭头。单继承怎么用都行,多继承先画 MRO、再让人人调用 super().__init__()——做到这两条,Python 的多继承比 Java 接口还好使。
下一篇聊元类:type 到底是怎么把一个 class 造出来的。
系列文章:Python 底层设计逻辑|实验数据均为 CPython 3.10.0 / 3.13.14 实测,可复现。

浙公网安备 33010602011771号