python:MRO的計算方式

我們都已經知道,Python 3(Python 2 的新式類)中多繼承模式是使用 C3 算法來確定 MRO(Method Resolution Order) 的。

下面就講解C3算法具體是怎麼計算的。

MRO計算規則

首先來定義一些符號:

用CN表示一個類:C1, C2, C3, ..., CN
C1 C2 C3 ... CN 表示的是一個包含多個類的列表

其中:

head = C1
tail = C2 ... CN

加法運算:

C + (C1 C2 ... CN) = C C1 C2 ... CN

L[C]表示類C的線性化,其實就是C的MRO,比如有個類:

class C(B1, B2, ..., BN): pass

那麼:

L[C(B1 ... BN)] = C + merge(L[B1] ... L[BN], B1 ... BN)

如果C是沒有父級的object類,則線性化是簡單的:

L[object] = object

merge的計算規則如下:

  1. take the head of the first list, i.e L[B1][0];
  2. if this head is not in the tail of any of the other lists, then add it to the linearization of C and remove it from the lists in the merge, otherwise look at the head of the next list and take it, if it is a good head.
  3. Then repeat the operation until all the class are removed or it is impossible to find good heads. In this case, it is impossible to construct the merge, Python 2.3 will refuse to create the class C and will raise an exception.

如果類C僅有一個父類,在這種情況合併的計算是簡單的

L[C(B)] = C + merge(L[B], B) = C + L[B]

計算MRO

先從一個簡單的例子開始說:

class B(object): pass

L[B] = L[B(object)]
     = B + merge(L[object], object)
     = B + L[object]
     = B object
    
>>> B.mro()
[<class '__main__.B'>, <type 'object'>]

簡單子類:

class C(B): pass

L[C] = L[C(B)]
     = C + merge(L[B], B)
     = C + L[B]
     = C B object    # 從上面已經知道了L[B] = B object
    
>>> C.mro()
[<class '__main__.C'>, <class '__main__.B'>, <class 'object'>]

下面看一個複雜的例子:

O = object
class F(O): pass
class E(O): pass
class D(O): pass
class C(D, F): pass
class B(D, E): pass
class A(B, C): pass

很容易計算的是:

L[O] = O = object
L[F] = L[F(O)] = F O
L[E] = L[E(O)] = E O
L[D] = L[D(O)] = D O

然後來計算相對複雜的C,B,A:

L[C]:

L[C] = L[C(D, F)]
     = C + merge(L[D], L[F], DF)
     # 從前面可知 L[D] 和 L[F] 的結果
     = C + merge(DO, FO, DF)
     # 因爲 D 是順序在第一個並且在幾個包含 D 的 list 中是 head
     # 所以這一次取 D 同時從列表中刪除 D
     = C + D + merge(O, FO, F)
     # 因爲 O 雖然是順序第一個但在其他 list(FO) 中不是head,則跳過,
     # 改爲檢查第二個list FO
     # F 是第二個 list 和其他 list 的 head
     # 取 F 同時從列表中刪除 F
     = C + D + F + merge(O)
     = C D F O

>>> C.mro()
[<class '__main__.C'>, <class '__main__.D'>, <class '__main__.F'>, <class 'object'>]

L[B]

L[B] = L[B(D, E)]
     = B + merge(L[D], L[E], DE)
     = B + merge(DO, EO, DE)
     = B + D + merge(O, EO, E)
     = B + D + E + merge(O)
     = B D E O

>>> B.mro()
[<class '__main__.B'>, <class '__main__.D'>, <class '__main__.E'>, <class 'object'>]

L[A]

L[A] = L[A(B, C)]
     = A + merge(L(B), L(C), BC)
     = A + merge(BDEO, CDFO, BC)
     = A + B + merge(DEO, CDFO, C)
     # 因爲第一個 list 的 head D 不是其他 list(CDFO) 的 head
     # 所以改爲從下一個 list CDFO 開始
     = A + B + C + merge(DEO, DFO)
     = A + B + C + D + merge(EO, FO)
     = A + B + C + D + E + merge(O, FO)
     = A + B + C + D + E + F + merge(O)
     = A B C D E F O

>>> A.mro()
[<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.E'>, <class '__main__.F'>, <class 'object'>]

並非所有類都接受線性化,在複雜的層次結構中,有些情況下無法計算出一個類的mro,比如下面這個例子:

O = object
class X(O): pass
class Y(O): pass
class A(X,Y): pass
class B(Y,X): pass

假如要從A和B派生新的類C,我們來嘗試計算L[C]

L[C] = L[C(A, B)]
     = C + merge(L[A], L[B], AB)
     = C + merge(AXYO, BYXO, AB)
     = A + B + merge(XYO, YXO)

此時,我們無法合併列表XYO和YXO,因爲X在YXO的尾部,而Y在XYO的尾部,因此沒有一個符合要求的類,所以C3算法停止。

在python2.3之後的版本中創建這個類時會引發錯誤,並拒絕創建這個類。

參考資料

The Python 2.3 Method Resolution Order | Python.org

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章