用python實現MRO算法


引子:

 

如圖反映了python3中,幾個類的繼承關系和查找順序。對於類A,其查找順序為:A,B,E,C,F,D,G,(Object),這並不是一個簡單的深度優先或廣度優先的規律。那么這個順序到底是如何產生的?

C3線性是用於獲取多重繼承下繼承順序的一種算法。通常,被稱為方法解析順序,即MRO(method resolution order)

算法的名字“C3”並不是縮寫,而是指該算法的三大重要屬性:

1.前趨圖。作為有向無環圖,找不到任何的循環,通常用前趨圖來理解程序的依賴關系。

2.保持局部的優先次序。

3.單調性。

C3是1996年首次被提出。在python2.3及后續版本中,C3被選定為默認的解析算法。

一個類的C3線性表,是由兩部分進行merge操作得到的,第一部分是是它所有父類的C3線性表(parents' linearizations),第二部分是它所有父類組成的列表(parents list)。后者其實是局部的優先級列表。

所謂merge操作,遵循以下原則:表的首個元素不可以出現在其他地方,如果出現了這樣的情形,那么就要將該元素全部移出,放到產出列表(output list)中。如果循環進行這一操作,就可以把所有的表逐步移出,逐步擴張產出表,最后得到一個純粹的產出表。這個產出表就是最后的C3線性表。

舉個例子:

python3代碼:

class O:
    pass
class A(O):
    pass
class B(O):
    pass
class C(O):
    pass
class D(O):
    pass
class E(O):
    pass
class K1(A, B, C):
    pass
class K2(D, B, E):
    pass
class K3(D, A):
    pass
class Z(K1, K2, K3):
    pass

即:

O從以下類繼承:無(實際上python3中默認為object類,因為所有類繼承於object類,所以才有多種多樣的內置方法可用)

A從以下類繼承:O

B從以下類繼承:O

C從以下類繼承:O

D從以下類繼承:O

E從以下類繼承:O

K1從以下類繼承:A,B,C

K2從以下類繼承:D,B,E

K3從以下類繼承:D,A

Z從以下類繼承:K1,K2,K3

為方便起見,記類cls的線性表為L[cls]。

首先,從最簡單的類O開始:

L[O]:平凡的情形,直接定為列表[O],即線性表的第一項是自身。所以,L[0]=[O]

L[A]:類A的所有父類是O,所以前一部分是L[O],后一部分是類A所有父類列表[O],前面已經得出L[O]=[O],因此L[A] = [A] + merge(L[O] + [O]) = [A]+merge([O] + [O]) = [A] + [O] = [A,O]

同理:

L[B]=[B,O]

L[C]=[C,O]

L[D]=[D,O]

L[E]=[E,O]

L[K1]:線性表第一項為自身K1,以后的項為其所有父類C3線性表和其所有父類列表的並——

K1繼承於A,B,C,所以所有父類C3線性表為:L[A],L[B],L[C];所有父類列表為:A,B,C。

並起來就是merge(L[A],L[B],L[C],A,B,C),然后,遵循原則一步步將其拆開。

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

=[K1]+merge([A,O],[B,O],[C,O],[A,B,C])——元素A只在這些列表的首項出現(如:[A,O]和[A,B,C]),應當把它移除到產出列表(output list)。

=[K1,A]+merge([O],[B,O],[C,O],[B,C])——元素O在列表的首項出現過(如:[O]),也在有些列表的剩余項出現過(如[B,O],[C,O]),所以保留它。但是,元素B只在這些列表的首項出現(如:[B,O],[B,C]),應當移出它。

=[K1,A,B]+merge([O],[O],[C,O],[C])——移出B后,同理發現C也是要移出的

=[K1,A,B,C]+merge([O],[O],[O])——merge操作已經走到盡頭了

=[K1,A,B,C,O]

L[K2]:K2繼承於D,B,E,所以所有父類C3線性表為L[D],L[B],L[E],所有父類列表為D,B,E。同理可得:

L[K2]=[K2]+merge([D,O],[B,O],[C,O],[D,B,E])

=[K2,D]+merge([O],[B,O],[C,O],[B,E])

=[K2,D,B]+merge([O],[O],[C,O],[E])

=[K2,D,B,E]+merge([O],[O],[O],[O])

=[K2,D,B,E,O]

L[K3]:K3繼承於D,A,所以所有父類的C3線性表為L[D],L[A],所有父類列表為D,A。同理可得:

L[K3]=[K3,D,A,O]

L[Z]:Z繼承於K1,K2,K3。前面計算了K1,K2,K3的線性表,所以這里直接代入計算:

L[Z]=[Z]+merge(L[K1],L[K2],L[K3],K1,K2,K3)

=[Z]+merge([K1,A,B,C,O] , [K2,D,B,E,O] , [K3,D,A,O] , [K1,K2,K3])——應移出K1

=[Z,K1]+merge([A,B,C,O],[K2,D,B,E,O],[K3,D,A,O],[K2,K3])——應移出K2

=[Z,K1,K2]+merge([A,B,C,O],[D,B,E,O],[K3,D,A,O],[K3])——應移出K3

=[Z,K1,K2,K3]+merge([A,B,C,O],[D,B,E,O],[D,A,O])——應移出D

=[Z,K1,K2,K3,D]+merge([A,B,C,O],[B,E,O],[A,O])——應移出A

=[Z,K1,K2,K3,D,A]+merge([B,C,O],[B,E,O],[O])——應移出B

=[Z,K1,K2,K3,D,A,B]+merge([C,O],[E,O],[O])——應移出C

=[Z,K1,K2,K3,D,A,B,C]+merge([O],[E,O],[O])——應移出E

=[Z,K1,K2,K3,D,A,B,C,E]+merge([O],[O],[O])——耗盡,結束

=[Z,K1,K2,K3,D,A,B,C,E,O]

在python3中使用對類help()函數,可以很方便地查看MRO:

可以看出,python3中的MRO計算,不能以簡單地找完一層再找上一層。假如以“廣度優先、從左到右、絕不重復”這一規律概括,很容易誤認為按照如下順序查找:

Z從K1,K2,K3繼承,所以前三項為K1,K2,K3。接下來找K1的父類A,B,C。再找K2的父類D,B,E,再找K3的父類D,A。但是這樣就造成重復。為防止重復,還得定義其他規范。

最后,利用python實現mro的生成。代碼可用,但是用了遞推函數,有機會以生成器的方式優化防止棧溢出。

 

 1 def not_in_tail(t, L):  2     # 判斷一個元素是不是在一個列表的尾巴中出現過。如果從未出現,返回真。
 3     if not L:  4         return True  5     if len(L) == 1:  6         return True  7     if t in L[1:]:  8         return False  9     else: 10         return True 11 
12 
13 def mro(cls): 14     # 如果一個類沒有任何父類,那么它的線性表里只有它自己。其實這個類就是object
15     if not cls.__bases__: 16         return [cls, ] 17     # 如果一個類只有一個父類object,那么它的線性表里是先找它自己,再找object
18     if cls.__bases__ == (object,): 19         return [cls, object] 20     # output用於產出線性表,第一項肯定是該類自己。
21     output = [cls, ] 22     # 這里使用遞歸方法,拿到它所有父類的線性表。后一項為所有父類的列表。
23     merge = [mro(parent) for parent in cls.__bases__] + [list(cls.__bases__), ] 24     while True: 25         # merge操作過程中會不斷地把元素取出,可能會有子列表被取空,這時候應直接刪除
26         while [] in merge: 27  merge.remove([]) 28         # merge操作的終極目標,就是全部剩下object,這就是while的終止條件
29         if all([t == [object, ] for t in merge]): 30             merge = [object, ] 31             break
32         # 准備將欲取出的元素放在head中。該行是一個變量初始化。
33         head = None 34         # 遍歷所有的子列表,同時還要拿到索引。
35         for index, sublist in enumerate(merge): 36             # 如果當前子列表只有object,那么就跳過
37             if sublist == [object, ]: 38                 continue
39             # 判斷子列表的第一項是否滿足條件:從未在任何列表的尾巴中出現。如果滿足此條件,記下此元素,退出循環准備刪除
40             if all([not_in_tail(sublist[0], l) for l in merge[index:]]): 41                 head = sublist[0] 42                 break
43         if head: 44             # 將該元素添加到線性表中
45  output.append(head) 46             # 將該元素從所有子列表中刪除
47             for l in merge: 48                 if head in l: 49  l.remove(head) 50     # 從最終返回的列表可以看出產生線性表的兩部分結構。merge的終極目標就是只剩下[object,],補上即可
51     mro_list = output + [object, ] 52     return mro_list 53 
54 # 以下是測試用例
55 class O: 56     pass
57 
58 
59 class A(O): 60     pass
61 
62 
63 class B(O): 64     pass
65 
66 
67 class C(O): 68     pass
69 
70 
71 class D(O): 72     pass
73 
74 
75 class E(O): 76     pass
77 
78 
79 class K1(A, B, C): 80     pass
81 
82 
83 class K2(D, B, E): 84     pass
85 
86 
87 class K3(D, A): 88     pass
89 
90 
91 class Z(K1, K2, K3): 92     pass
93 
94 
95 print(mro(Z)) 96 
97 print(mro(O))

輸出結果為:

1 [<class '__main__.Z'>, <class '__main__.K1'>, <class '__main__.K2'>, <class '__main__.K3'>, <class '__main__.D'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.E'>, <class '__main__.O'>, <class 'object'>] 2 
3 [<class '__main__.O'>, <class 'object'>]

可以通過__mro__方法驗證:

1 print(Z.__mro__) 2 
3 (<class '__main__.Z'>, <class '__main__.K1'>, <class '__main__.K2'>, <class '__main__.K3'>, <class '__main__.D'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.E'>, <class '__main__.O'>, <class 'object'>)

當然,__mro__方法返回的是元組。所以前面的python代碼可以利用tuple()改成以元組形式返回。在遞推時,加一層list()以元組形式傳入。不再展開。

回到開頭的引子。經過驗證,答案完全正確:

class G:pass
class E(G):pass
class B(E):pass
class F(G):pass
class C(F):pass
class D(G):pass
class A(B,C,D):pass

print(mro(A))
print(A.__mro__)

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

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM