在上一篇文章中分析了 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 是将一组列表输出为一个列表,其过程为:

  1. 检查第一个列表的头元素,记做 H;
  2. 若 H 未出现在其他列表的尾部,则将其输出,并将其从所有列表中删除,然后回到步骤1;否则去,取下一个列表的头部记作H,继续该步骤
  3. 重复上述步骤,直至列表为空或者不能再找出可以输出的元素。如果列表为空,则算法结束,如果列表不为空,并且无法找出可以输出的元素,那么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)

说明上述推导没有问题。

参考资料