跳转至

Python 多继承的 MRO C3 算法原理是什么,super() 在多继承中如何工作?

多继承不是乱炖,Python 靠 C3 线性化 定规矩。下面用最精要的方式拆解,保证你听完就能讲给同事听。


🧬 MRO 是什么?

Method Resolution Order —— 方法解析顺序。 当你在 class D(B, C) 里调 self.method(),Python 必须知道先去 B 还是 C 找。 MRO 就是那个排好序的类列表,让每次查找都是确定的、无歧义的。


🔢 C3 算法原理(一句话版)

MRO = 自己 + 合并所有父类的 MRO + 父类本身

合并规则(merge)是精髓:

  1. 取第一个列表的头(第一个元素)

  2. 如果这个头没有出现在任何其他列表的尾部,就把它拿出来,并从所有列表中删除这个头

  3. 否则,看下一个列表的头

  4. 循环直到所有列表为空

  5. 如果找不到合法头 → 💥 抛出 TypeError,拒绝创建不一致的继承

公式感: L[C] = C + merge(L[B1], L[B2], ..., B1, B2, ...)

经典钻石: D(B, C)B(A), C(A)

  • L[A] = A, object

  • L[B] = B, A, object

  • L[C] = C, A, object

  • L[D] = D + merge(B,A,object , C,A,object , B,C) → 头 B 不在其他尾部 → 取出 B → 下一头 C 不在其他尾部 → 取出 C → 然后 A、object 最终 MRO:D → B → C → A → object ✅


🚀 super() 的协同舞蹈

super() 不是直接调父类,而是沿着 MRO 链,找当前类后面的下一个类。 这造就了多重继承下“协作”模式:

class A:
    def action(self):
        print("A")

class B(A):
    def action(self):
        super().action()
        print("B")

class C(A):
    def action(self):
        super().action()
        print("C")

class D(B, C):
    def action(self):
        super().action()
        print("D")

D().action() 输出顺序(MRO: D→B→C→A):

A
C
B
D

🔁 调用链是一根线穿到底:D 的 super 调 B,B 的 super 调 C,C 的 super 调 A,A 结束回溯。

这就是“协同”的意义:每个类只负责自己的事,把接力棒交给 MRO 链的下一个,完全不依赖具体父类是谁。


⚠️ 三个致命坑

1️⃣ 参数不匹配爆炸 如果 A.init(self, name) 需要参数,而 B、C 的 super 传了不同参数,直接在 C 那里崩掉。 ✅ 解法:所有协作类都接受 **kwargs,用关键字参数透明传递。

2️⃣ 断链 B 调了 super() 但 C 没调,链就在 C 断了,A 永远收不到调用。 ✅ 必须保证从叶子到根的所有类都用 super(),否则别混用。

3️⃣ 静态调用父类的陷阱 写 B.action(self) 硬编码会打乱 MRO,C 被跳过,导致钻石继承失效。 ✅ 多继承下,禁止直接指名调用父类方法,一律走 super()


🧠 面试加分点(直接让面试官画勾)

  • C3 的稳定性:保证“本地优先”和“单调性”(如果 B 在 A 前面,那么任何子类里 B 始终在 A 前)。这是 Python 2 经典类 DFS 所没有的。

  • super 的双面手:super() 不带参数返回 bound 对象,自动绑定了 classself,避免 Python 2 的 super(D, self) 重复。

  • 工程实践:在 Mixin 模式中,Mixin 类必须放在“主继承链”前面,且都依赖 super() 协作。结合 Django 的 LoginRequiredMixin 说明更香。


最后扔给你一道现考题: 把上面例子改成 class D(C, B),MRO 会变成什么?输出顺序又是什么?想通它,C3 和 super 你就算吃透了。