在上一篇文章中分析了 Python 的 MRO 的变迁历史,参考 Python 方法解析顺序 MRO – 艽野尘梦
Python 从 2.3 开始就将 mro 换成了 C3,这里记录一下 C3 的具体过程。
原理
类 C 的线性化记为 L[C] = [C1, C2, … Cn]
,其中 C1 称为 L[C]
的头,其余元素 [C2, …, Cn]
称为尾。如果一个 类 C 继承自基类 B1, B2, …,Bn
, 那么 L[C]
的计算过程为:
L[object] = [object]
L[C(B1, B2, …, Bn)] = [C] + merge(L[B1], L(B2), …, L[Bn], [B1, B2, …, Bn])
merge 是将一组列表输出为一个列表,其过程为:
- 检查第一个列表的头元素,记做 H;
- 若 H 未出现在其他列表的尾部,则将其输出,并将其从所有列表中删除,然后回到步骤1;否则去,取下一个列表的头部记作H,继续该步骤
- 重复上述步骤,直至列表为空或者不能再找出可以输出的元素。如果列表为空,则算法结束,如果列表不为空,并且无法找出可以输出的元素,那么Python会抛出异常
TypeError: Cannot create a consistent method resolution
实践
从几个例子来理解一下这个算法:
class A(object): pass
class B(object): pass
class C(A, B): pass
object,A, B 的线性化结果比较简单:
L[object] = [obejct]
L[A] = [A, object]
L[B] = [B, object]
C 的线性化计算过程为:
L[C] = [C] + merge(L[A], L[B], [A, B])
= [C] + merge([A, object], [B, object], [A, B])
= [C, A] + merge[[object], [B, object], [B]] # 取出 A
= [C, A, B] + merge([object], [object]) # 取出 B
= [C, A, B, object] # 取出 object
merge[[object], [B, object], [B]] 首先取出的是 B 而不是 object,因为 object 虽然是第一个列表的头,但是它是第二个列表的尾之一。所以第一个列表会被跳过,检查第二个列表,而 B 是没有出现在其他列表的尾部的,所以输出的是 B。
看一个复杂的例子,集成关系如图所示
翻译为对应的代码为:
class A(object): pass
class B(object): pass
class C(object): pass
class D(A, C): pass
class E(B, C): pass
class F(D, E): pass
A、B、C、D、E 的线性化比较简单
L[A] = [A, object]
L[B] = [B, object]
L[C] = [C, object]
L[D] = [D, A, C, object]
L[E] = [E, B, C, object]
重点来推导下 F 的线性化:
L[F] = [F] + merge(L[D], L[E], [D, E])
= [F] + merge([D, A, C, object], [E, B, C, object], [D, E])
= [F, D] + merge([A, C, object], [E, B, C, object], [E])
= [F, D, A] + merge([C, object], [E, B, C, object], [E])
= [F, D, A, E] + merge([C, object], [B, C, object])
= [F, D, A, E, B] + merge([C, object], [C, object])
= [F, D, A, E, B, C, object]
我们通过 __mor__
方法验证一下结果:
In [3]: A.__mro__
Out[3]: (__main__.A, object)
In [4]: B.__mro__
Out[4]: (__main__.B, object)
In [5]: C.__mro__
Out[5]: (__main__.C, object)
In [6]: D.__mro__
Out[6]: (__main__.D, __main__.A, __main__.C, object)
In [7]: E.__mro__
Out[7]: (__main__.E, __main__.B, __main__.C, object)
In [8]: F.__mro__
Out[8]:
(__main__.F,
__main__.D,
__main__.A,
__main__.E,
__main__.B,
__main__.C,
object)
说明上述推导没有问题。