有讀者可能會好奇,為什麼 MRO 棄用了前兩種演算法,而選擇最終的 C3 演算法呢?原因很簡單,前 2 種演算法都存在一定的問題。有關舊式類和新式類的講解,可閱讀《Python super()使用注意事項》一文。
class A: def method(self): print("CommonA") class B(A): pass class C(A): def method(self): print("CommonC") class D(B, C): pass print(D().method())通過分析可以想到,此程式中的 4 個類是一個“菱形”繼承的關係,當使用 D 類物件存取 method() 方法時,根據深度優先演算法,搜尋順序為
D->B->A->C->A
。因此,使用舊式類的 MRO 演算法最先搜尋得到的是基礎類別 A 中的 method() 方法,即在 Python 2.x 版本中,此程式的執行結果為:舊式類的 MRO 可通過使用 inspect 模組中的 getmro(類名) 函數直接獲取。例如 inspect.getmro(D) 表示獲取 D 類的 MRO。
CommonA
但是,這個結果顯然不是想要的,我們希望搜尋到的是 C 類中的 method() 方法。D->B->A->C->A
,由於此順序中有 2 個 A,因此僅保留後一個,簡化後得到最終的搜尋順序為 D->B->C->A
。可以看到,這種 MRO 方式已經能夠解決“菱形”繼承的問題,但是可能會違反單調性原則。所謂單調性原則,是指在類存在多繼承時,子類不能改變基礎類別的 MRO 搜尋順序,否則會導致程式發生異常。新式類可以直接通過 類名.__mro__ 的方式獲取類的 MRO,也可以通過 類名.mro() 的形式,舊式類是沒有 __mro__ 屬性和 mro() 方法的。
class X(object): pass class Y(object): pass class A(X,Y): pass class B(Y,X): pass class C(A, B): pass通過進行深度遍歷,得到搜尋順序為
C->A->X->object->Y->object->B->Y->object->X->object
,再進行簡化(相同取後者),得到 C->A->B->Y->X->object
。CommonC
Traceback (most recent call last):
File "C:UsersmengmaDesktopdemo.py", line 9, in <module>
class C(A, B):
TypeError: Cannot create a consistent method resolution
order (MRO) for bases X, Y
這裡的關鍵在於 merge,它的運算方式如下:注意,以類 A 等式為例,其中 merge 包含的 A 稱為 L[A] 的頭,剩餘元素(這裡僅有一個 object)稱為尾。
L[B] = [B] + merge(L[A],[A]) = [B] + merge([A,object],[A]) = [B,A] + merge([object]) = [B,A,object]同理,其他類的 MRO 也可以輕鬆計算得出。這裡不再贅述,有興趣的讀者可自行推算。