天天看點

pyhon新式類多重繼承的mro順序确定: c3算法

本文寫作目的:試圖以最清晰簡潔的方式介紹python新式類的C3算法

python經典類和新式類:

 python中的新式類是指直接或間接繼承自object的類,而經典類則是沒有直接或間接繼承object的類,新式類和經典類的重要差別就是它們的MRO(方法解析順序)不同,經典類的mro順序是用深度優先+從左至右的方法确定的(這裡的"從左至右"是指定義子類時,多個基類的書寫順序從左至右),而新式類的MRO順序則是用C3算法确定的(2.3版本不是C3,之後都是C3)。在python3中隻支援新式類,即所有類都直接或者簡介繼承自object類,并且,定義一個類時如果沒有顯示寫出基類,則預設繼承自object類;在python2(2.3及以上)中則有經典類和新式類兩種方式,在python2中定義新式類必須顯示寫出繼承object類。

新式類的C3算法:

 方法解析順序MRO其實就是确定子類和祖先類之間的一個線性順序,這個順序表達了各個祖先類(包括父類)對于子類的重要性(優先級),當類對象調用某個方法時,解釋器會根據MRO清單去挨個找,直到找到一個同名的方法然後調用之,是以了解python繼承結構中各個類的優先順序對于寫代碼是必須的,C3算法過程如下。

 假設子類C的N個直接父類是B1,B2,…,BN,記L[C]是C的線性化清單(MRO表),L[B1],L[B2],…,L[BN]是N個父類的線性化清單,F=[B1,…,BN]是由N個父類構成的單個清單,其中B1,…,BN是按照定義類C時的書寫順序從左至右的。則L[C]就是由N+1個清單生成的,具體就是用下面的遞歸合并算法:

L[C] = [C] + merge(L[B1], L[B2] ,…, L[BN], F)

L[object] = object

其中的merge的輸入是N+1個清單,按照如下方式輸出一個長度為N的清單:

  1. 檢查第一個清單的頭元素(如 L[B1] 的頭),記作 H。
  2. 若 H 未出現在其它清單的中,或者出現了但也都是在頭部,則将其輸出,并将其從所有清單中删除,然後回到步驟1,否則,取出下一個清單的頭部記作 H,繼續該步驟。重複上述步驟,直至清單為空或者不能再找出可以輸出的元素。如果是前一種情況,則算法結束;如果是後一種情況,說明無法建構繼承關系,Python 會抛出異常。

可能第一次看這個過程不是很能了解,舉個栗子就很清晰啦。(圖是偷來的,嘻嘻),需要注意一下,圖中多個父類的左右順序和繼承順序是一緻的。

pyhon新式類多重繼承的mro順序确定: c3算法

如上圖的繼承結構,現在要求類A的MRO清單,記O為object,假設已經求出各個祖先類的MRO表如下:

L[D] = [D,O]
L[E] = [E,O]
L[F] = [F,O]
L[B] = [B,D,E,O]
L[C] = [C,D,F,O]
           

則L[A]的合并過程如下:

L[A] = A + merge([B,D,E,O],[C,D,F,O],[B,C]) # 取出B 
	 = [A,B] + merge([D,E,O],[C,D,F,O],[C]) # 取出C,因為第2個清單中D不是第1個元素
	 = [A,B,C] + merge([D,E,O],[D,F,O]) 	# 取出D
	 = [A,B,C,D] + merge([E,O],[F,O])		# 取出E
	 = [A,B,C,D,E] + merge([O],[F,O])		# 取出F
	 = [A,B,C,D,E,F] + merge([O],[O])		# 取出O
	 = [A,B,C,D,E,F,O]
           
# 下面是驗證代碼(python3中測試結果)
class D(object): pass
class E(object): pass
class F(object): pass
class B(D, E): pass
class C(D, F): pass
class A(B, C): pass
A.mro()
#[__main__.A, __main__.B, __main__.C, __main__.D, __main__.E, main__.F, object]
           

下面再舉個有沖突的栗子,繼承結構如下圖:

pyhon新式類多重繼承的mro順序确定: c3算法
L[X] = [X,O]
L[Y] = [Y,O]
L[A] = [A,X,Y,O]
L[B] = [B,Y,X,O]

L[C] = [C] + merge([A,X,Y,O],[B,Y,X,O],[A,B])
	 = [C,A] + merge([X,Y,O],[B,Y,X,O],[B])
	 = [C,A,B] + merge([X,Y,O],[Y,X,O]) # 此處X和Y沖突了,是以解釋器會抛出異常
           
# 驗證代碼
class X(object): pass
class Y(object): pass
class A(X, Y): pass
class B(Y, X): pass
class C(A, B): pass
           

結果如下,抛出異常了

pyhon新式類多重繼承的mro順序确定: c3算法

幾個說明:

 python新式類的C3算法能夠保證以下幾點:

  1. 線性單調性,是說如果一個類的MRO順序在它任意子(孫)類MRO清單中的相對順序是不變的,也就是說新定義類的MRO順序必須符合原有類的MRO順序。

  2. 局部優先級,是指幾個直接父類的在定義子類時的書寫順序的相對順序在子類中MRO中是保持一緻的。(局部優先級的含義我猜的是這個意思,不一定準喲,但是後面那句話是對的)。 

  3. MRO清單中父類永遠在子類後面。

 是以再計算一個繼承結構的MRO清單後,可以用這幾條去檢查一下,如果發現有不符合的,那這個繼承結構肯定是有問題的。

最後 :下面有兩篇參考文獻很棒,第一篇也是講c3算法的,還講了之前的mro算法為什麼不行,第二篇是c3算法的官方文檔。第一篇将c3算法時有一個錯誤的地方,就是他将父類的清單[B1,…,BN]寫成是N個單元素的清單了,即[B1],…,B[N],其實它們是構成一個清單[B1,…,BN],按照他那個說法,下面的例子應該是對的,但實際上抛出異常了。

class A(object):pass
class B(A):pass
class C(A,B):pass 	# class C(B,A):pass 是可以的

L[A] = [A,O]
L[B] = [B,A,o]
L[C] = C + merge([A,O],[B,A,O],[A],[B])
	 = [C,B] + merge([A,O],[A,O],[A])
	 = [C,B,A] + merge([O],[O])
	 = [C,B,A,O]
# 如果将父類的清單[B1,..,BN]寫成N個[B1],..,B[N],像上面的例子一樣,是錯誤的,因為此時
# 相當于沒有考慮父類的相對順序了
# 此例子當中,A是B的父類,是以A應該在B之後,但是同時C繼承A,B時,A在前,是以又應該是A在B
# 前,進而沖突了。

# 正确解法
L[C] = C + merge([A,O],[B,A,O],[A,B]) # 此步A和B就沖突了,是以繼承結構不合法
           

第一篇: https://hanjianwei.com/2013/07/25/python-mro/

第二篇: https://www.python.org/download/releases/2.3/mro/#bad-method-resolution-orders