bigywater.tech
Python · 19

super():它为什么不一定调用“父类方法”

构造D(B,C)菱形继承,验证B.greet()里的super().greet()调用到的是C而不是A,看super()到底是按什么规则决定调用谁的。

B→C不是B→A
super()调用到的是MRO里的下一个
按实例MRO
super()行为取决于运行时实例,不是类定义
协作式多继承
设计目的是让整条MRO链都能被走完

做的事:四个类,每一层都调用super(),追踪完整的调用链

B(A)、C(A)、D(B,C),每个类的greet都调用super().greet()。

代码class A: def greet(self): print("A.greet 被调用") class B(A): def greet(self): print("B.greet 被调用,调用super()...") super().greet() class C(A): def greet(self): print("C.greet 被调用,调用super()...") super().greet() class D(B, C): def greet(self): print("D.greet 被调用,调用super()...") super().greet()

验证:B.greet()里的super(),调用到的不是A

这是本文最核心的发现。

结果D.__mro__: ['D', 'B', 'C', 'A', 'object'] 调用d.greet(),完整输出: D.greet 被调用,调用super()... B.greet 被调用,调用super()... C.greet 被调用,调用super()... ← 是C,不是A! A.greet 被调用

的调用链是 D→B→C→A,不是"D调用B,B调用它自己的父类A"。B 在类定义上唯一声明的父类确实是 A(class B(A)),但 super().greet() 不是"调用B声明的父类的greet",而是"沿着当前实例d的完整MRO,找到B的下一个位置,调用那个类的greet"——因为这次调用的实例是 d(一个D的实例),它的MRO是D→B→C→A,B的下一个是C,所以 super() 在这里调用到了C,跟B自己在定义时声明的父类完全没关系。

这就是 super() 容易被误解的地方:很多人以为 super().method() 等价于"调用我父类的这个方法",但它调用的是"当前实例的MRO里,我后面那一个"——如果这个实例是通过多继承创建的子类实例,"我后面那一个"可能是一个跟我毫无继承关系(同辈)的兄弟类,不是我自己声明的父类。super() 的行为取决于运行时这个具体实例的MRO,不是类定义时写的继承关系本身。

为什么这样设计:让协作式多继承能一路走完

如果super()只认"类定义时声明的父类",会发生什么。

如果 B.greet() 里的 super() 死板地指向 A,那么在 D(B,C) 这个组合里,C.greet() 就永远没有机会被调用到——调用链会是 D→B→A,C被跳过。这在需要"每一层都做一点初始化/清理工作,然后交给下一层继续"的场景(比如多个mixin类协作)下是致命的。super() 按MRO走,才能保证不管这个类被用在多复杂的继承组合里,只要每一层都老老实实调用 super(),整条链都能完整地走下去,谁都不会被落下。

分步演示:super()调用链的完整轨迹

每一步都是本文上面用Python 3.14跑出来的原始输出。

super()按MRO走,不是按类定义时声明的父类走
这一步新验证的调用

    本文用到的工具

    • 本机 Python 3.14.7
    • super() / __mro__:验证super()按MRO而非声明父类调用
    京ICP备2026067502号-1京公网安备11010502063487号