Python 支持多继承,而多继承带来灵活性的同时,也会产生著名的 菱形继承冲突(Diamond Problem) 和方法查找混乱的问题。为了在复杂继承体系中保持可预测性、一致性和安全性,Python 采用了 C3 Linearization(C3 线性化算法)作为其 MRO(Method Resolution Order,方法解析顺序)的计算方法。
本文会详细讲解 Python 的 C3 Linearization(C3 线性化算法),包括:
- 为什么 Python 需要 C3
- C3 的三大原则
- 核心算法步骤(merge)
- 手算公式 & 例子
- 复杂多继承案例
- 算法正确性的直观解释
一、为什么需要 C3?
Python 支持多继承(multiple inheritance) ,而多继承会带来最经典的问题------菱形继承冲突(diamond problem)。
例如:
A
/ \
B C
\ /
D
问题:
-
D().method()应该按什么顺序查找?B → A → C?还是C → A → B? -
如何防止 A 被调用两次?
-
如果出现以下结构,又应该如何处理?
class C(A, B)
class D(B, A)
class E(C, D) # 直接冲突
如果没有统一的规则,查找顺序可能混乱甚至自相矛盾。
C3 正是为了解决这些问题的算法。
二、C3 Linearization 的三大原则
C3 线性化的输出是一个列表,即一个类的 MRO(方法解析顺序)。
C3 保证满足:
① 本地优先(Local Precedence Order)
类定义时指定的父类顺序必须保留。
python
class C(A, B): ...
则 C 的 MRO 必须保证:
A 在 B 前
不能被任何祖先类打乱。
② 子类优先于父类(Subclass before Superclass)
类应排在自己的父类之前:
C 在 A 之前
C 在 B 之前
③ 单调性(Monotonicity)
父类的 MRO 必须保持一致,不允许因为多继承而改变父类自己的解析规则。
否则继承体系就会混乱。
三、C3 算法核心:merge()
C3 算法最关键的一步是 merge 函数,它合并多个列表并保持局部顺序一致。
Python 计算类 L 的 MRO 时:
L.mro() = [L] + merge(parent_lists, parents_mros)
例如:
python
class D(B, C):
pass
则要 merge:
parents = [B, C]
B.mro() = [B, A, object]
C.mro() = [C, A, object]
最终:
D.mro() = [D] + merge([B, C], [B, A, object], [C, A, object])
四、C3 merge() 的规则
merge 步骤:
每一轮:
- 取所有列表的 首元素(head)
- 选择一个"合法"候选 head:
- 该 head 不在任何其它列表的 tail(尾部)中
- 将这个 head 加入结果 MRO
- 从所有列表头部删除该 head
- 重复,直到所有列表为空
- 如果无法找到合法 head → 抛出 MRO 冲突错误
五、按照 C3 手算一个经典例子
例子:
A
/ \
B C
\ /
D
代码:
python
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
print(D.mro())
父类顺序:
parents = [B, C]
父类 MRO:
B.mro() = [B, A, object]
C.mro() = [C, A, object]
merge 输入:
1) [B, C]
2) [B, A, object]
3) [C, A, object]
▶ 第 1 轮:候选 head = B、B、C
检查 B:
- B 是否出现在其他列表的 tail(即
[A, object],[A, object])? - 不在 → 所以选 B
输出:
[D, B]
删除所有列表中出现的 B:
[ C]
[ A, object]
[ C, A, object]
▶ 第 2 轮:候选 head = C、A、C
检查 C:
- C 是否在其它 tail?tail =
[A, object], [A, object] - C 不在 → 选 C
输出:
[D, B, C]
删除 C:
[]
[A, object]
[ A, object]
▶ 第 3 轮:候选 head = A、A
A 不在任何 tail(只有 object)
输出:
[D, B, C, A]
删除 A:
[]
[object]
[object]
▶ 第 4 轮:选 object
最终 MRO:
[D, B, C, A, object]
和 Python 完全一致:
python
print(D.mro())
六、C3 能检测继承冲突(无法线性化时)
例子:
python
class A: pass
class B: pass
class C(A, B): pass
class D(B, A): pass # 反过来
class E(C, D): pass # 冲突
MRO 无法计算,会报:
TypeError: Cannot create a consistent method resolution order
为什么?(具体推导见文末)
- C 要求 A → B
- D 要求 B → A
- E 同时继承 C 和 D,顺序互相矛盾 → 无法满足 C3 单调性 → 直接报错
C3 确保继承体系不会产生乱序。
七、C3 的意义:它解决了哪些问题?
1. 解决菱形继承中的"重复父类调用"问题
在 D 的 MRO 中,A 只出现一次:
[D, B, C, A, object]
super() 调用链也只会调用一次 A。
2. super() 不再是"父类调用器",而是"按 MRO 下一个类调用器"
例如:
python
class D(B, C)
super() 顺序:
D → B → C → A
不会重复调用父类,也不会跳序。
3. 保证继承关系"单调",不会因多继承破坏父类自己的 MRO 顺序
例如:
python
class C(A, B) # A 在 B 前
子类无法让 B 排在 A 前(除非换继承顺序)
八、总结
1、C3 Linearization 是 Python 用来计算 MRO 的算法
2、它通过 merge() 合并父类 MRO 列表
3、三大原则:
- 本地顺序(Local precedence)
- 继承顺序不变(Preserve parent order)
- 单调性(Monotonicity)
每次从多个候选列表的头部,挑出一个 "不出现在任何其它列表尾部"的类 加入 MRO。
这些规则保证了多继承结构的正确性与一致性。
C3 检测继承冲突推导
当C3 检测继承冲突时,我们把这个冲突例子 按 C3 算法一步一步算一遍,精确指出"卡死"的那一步。
先给出类层次:
python
class A: pass
class B: pass
class C(A, B): pass # C 要求 A 在 B 前
class D(B, A): pass # D 要求 B 在 A 前
class E(C, D): pass # 这里会报 MRO 冲突
第 1 步:先算 C 的 MRO
class C(A, B)
根据 C3 定义:
L[C] = [C] + merge( L[A], L[B], [A, B] )
已知:
- L[A] = [A, object]
- L[B] = [B, object]
所以要 merge 这三个列表:
[A, object][B, object][A, B]
merge 过程:
- 轮次 1:候选头是 A、B、A
- 看 A 是否出现在其他列表的尾部(tails):
[object]、[object]、[B],都没有 A
→ 选 A
- 看 A 是否出现在其他列表的尾部(tails):
- 删除各列表的 A:
[object][B, object][B]
- 轮次 2:候选头是 object、B、B
- 检查 B:
- 尾部是
[object]、[object]、[],没有 B
→ 选 B
- 尾部是
- 检查 B:
- 删除 B:
[object][object][]
- 轮次 3:只剩 object → 选 object
最终:
text
L[C] = [C, A, B, object]
这体现了 "C 要求 A 在 B 前"。
第 2 步:再算 D 的 MRO
class D(B, A)
按同样规则:
L[D] = [D] + merge( L[B], L[A], [B, A] )
- L[B] = [B, object]
- L[A] = [A, object]
要 merge:
[B, object][A, object][B, A]
merge 过程:
- 轮次 1:候选头 B、A、B
- B 是否出现在其他 tail 中?
- 尾部
[object]、[object]、[A]中都没有 B
→ 选 B
- 尾部
- B 是否出现在其他 tail 中?
- 删除 B:
[object][A, object][A]
- 轮次 2:候选头 object、A、A
- 检查 A:
- 尾部
[ ]、[object]、[]中均无 A
→ 选 A
- 尾部
- 检查 A:
- 删除 A:
[object][object][]
- 轮次 3:只剩 object → 选 object
最终:
text
L[D] = [D, B, A, object]
这体现了 "D 要求 B 在 A 前"。
第 3 步:现在算 E 的 MRO ------ 冲突就出在这里
class E(C, D)
C3 定义:
L[E] = [E] + merge( L[C], L[D], [C, D] )
我们已经有:
L[C] = [C, A, B, object]L[D] = [D, B, A, object]- 父类列表
[C, D]
因此需要 merge 这三条序列:
- S1 =
[C, A, B, object] - S2 =
[D, B, A, object] - S3 =
[C, D]
下面是 关键步骤,我们逐轮执行 C3 的 merge 规则:
在所有序列的"头元素"中,选择一个 不出现在任何序列"尾部"中的元素 作为下一个 MRO 元素。
🔹 第 1 轮:
当前序列:
[C, A, B, object][D, B, A, object][C, D]
候选头(heads):
- S1 头:C
- S2 头:D
- S3 头:C
检查 C 是否出现在任何 tail 中:
- S1 tail:
[A, B, object] - S2 tail:
[B, A, object] - S3 tail:
[D]
C 不在任何 tail 中 → ✅ 合法,选 C。
MRO 目前为:
text
L[E] = [E, C, ...]
从所有序列头部删除 C:
- S1 →
[A, B, object] - S2 →
[D, B, A, object](未变) - S3 →
[D]
🔹 第 2 轮:
序列:
[A, B, object][D, B, A, object][D]
候选头:
- S1 头:A
- S2 头:D
- S3 头:D
先看 A:
- tail1 =
[B, object] - tail2 =
[B, A, object] - tail3 =
[]
A 出现在 tail2([B, A, object])中 → ❌ 不合法,不能选 A。
再看 D:
- tail1 =
[B, object] - tail2 =
[B, A, object] - tail3 =
[]
D 不在任何 tail 中 → ✅ 合法,选 D。
MRO 目前为:
text
L[E] = [E, C, D, ...]
删除所有序列中的 D:
- S1 →
[A, B, object](未变) - S2 →
[B, A, object] - S3 →
[](空序列可忽略)
🔹 第 3 轮 ------ 冲突出现的地方
现在序列:
- S1 =
[A, B, object] - S2 =
[B, A, object]
候选头:
- S1 头:A
- S2 头:B
我们依次检查:
检查候选 A:
- tail1 =
[B, object] - tail2 =
[A, object]
A 是否出现在任一 tail 中?
→ 是的,出现在 tail2([A, object])里。
根据 C3 规则:如果某个候选 head 出现在其它序列的 tail 中,就不能选它。
所以 A ❌。
再检查候选 B:
- tail1 =
[B, object] - tail2 =
[A, object]
B 是否出现在任一 tail 中?
→ 是的,出现在 tail1([B, object])里。
所以 B 也 ❌。
🔴 结论:这一轮 没有任何合法的 head 可以选
C3 算法规定:
如果在某一轮中,所有列表的头元素都出现在某个列表的尾部,则无法选出下一个类,说明继承关系 不一致(inconsistent) ,直接抛
TypeError。
这就是 Python 报:
text
TypeError: Cannot create a consistent method resolution order (MRO) for bases A, B
的根本原因。
用"约束"角度再解释一下这一步为何必挂
-
从
L[C] = [C, A, B, object]可以看出:A 必须在 B 之前(约束:A → B)
-
从
L[D] = [D, B, A, object]可以看出:B 必须在 A 之前(约束:B → A)
而在 E 的第三轮时,剩下的必须排 A 和 B 的相对顺序,但我们必须同时满足:
text
A 在 B 前 (来自 C)
B 在 A 前 (来自 D)
这是一个明显的逻辑矛盾,所以 C3 算法在那一轮发现"没有合法 head",测出继承关系不可能线性化,立刻报错。