super() 在多继承中按 MRO 动态查找下一方法,菱形继承需所有类统一用 super() 和 **kwargs,避免硬调父类或参数不一致导致链断裂、重复初始化或崩溃。

super() 在多继承中不是简单“找父类”,而是走 MRO 链
Python 的 super() 不是静态指向某个父类,而是根据当前类的 MRO(Method Resolution Order)列表,从调用位置往后找下一个匹配的方法。在菱形继承(A ← B, C ← D)中,D 的 MRO 是 [D, B, C, A, object],所以 super().__init__() 在 B.__init__() 里不会跳去 C,而是按序走到下一个——也就是 C.__init__()(如果 B 里写了 super().__init__()),前提是所有类都用 super() 且签名一致。
常见错误现象:A.__init__() 被执行两次,或压根没被执行——根本原因是部分类用了 Parent.__init__(self) 硬调,打断了 super() 的链式传递。
- 所有参与菱形继承的类,
__init__必须接受**kwargs(或至少兼容多余参数),否则super()向下传参会失败 - 只要有一个类写
A.__init__(self),它后面的super()就失去上下文,MRO 链在此断裂 - 用
D.mro()实时查看顺序,别凭印象猜
__init__ 参数不统一导致 super 链中途崩溃
当 B.__init__(self, x)、C.__init__(self, y)、A.__init__(self) 参数不一致时,super().__init__(x, y) 这种写法必然出错:A.__init__() 接不住两个参数,而 B 和 C 又各自只消费一个,没人负责把参数“过滤”后往下传。
典型报错:TypeError: __init__() takes 2 positional arguments but 3 were given,或更隐蔽的 unexpected keyword argument。
- 统一用
**kwargs接收,内部用kwargs.pop('x', None)提取自己需要的参数 - 确保每个
super().__init__(**kwargs)都把未消费的kwargs原样传下去 - 不要在中间层做参数校验(如
if 'x' not in kwargs: raise ValueError),这会让下游无法补全
手动调用父类 __init__ 与 super 混用等于自毁 MRO
有人想“保险起见”,在 B.__init__ 里既写 super().__init__(**kwargs),又加一行 A.__init__(self) ——这是最危险的组合。前者按 MRO 走到 C,后者硬调 A,结果 A.__init__() 执行两次,状态被覆盖,对象可能处于半初始化状态。
更糟的是,如果 A.__init__() 有副作用(比如注册回调、分配资源),重复执行可能引发异常或内存泄漏。
- 整个继承链中,要么全部用
super(),要么全部不用;混合使用没有中间态 -
object.__init__()接受任意参数但忽略它们,所以即使传了多余参数也不会报错——但这只是掩盖问题,不代表逻辑正确 - 用
print(self.__class__.__name__)在各__init__开头打点,能快速验证是否重复/遗漏
为什么 inspect.getmro(D) 和 D.mro() 结果一样,但行为还是不对?
D.mro() 显示顺序没问题,不代表 super() 就一定按预期工作。真正决定调用路径的,是 super() 被调用时的「当前类」和「实例类型」。例如,在 B.__init__ 中写 super(B, self).__init__(**kwargs),它会从 B 在 MRO 中的位置往后找——即跳过 B 自己,从 C 开始搜;但如果误写成 super(D, self).__init__(**kwargs),就会从 D 之后开始找(即 B),完全绕开本该触发的逻辑。
- 永远用无参
super()(Python 3),它自动绑定当前类和self - 避免显式传参,尤其别传比当前类更靠前的类(如在
B里传D) - MRO 是静态的,但
super()的解析是动态的——取决于调用栈上“此刻正在执行哪个类的方法”
最易被忽略的一点:菱形继承的健壮性不取决于你写了多少 super(),而取决于所有分支是否对参数、副作用、调用时机达成隐式契约。少一个 **kwargs,就可能让整条链静默失效。









