12 Python MRO
Python在多重繼承時,如果父類中存在同名函式會產生二義性,Python中處理這個問題方法就是MRO.
如果對MRO發展史沒興趣的話,直接看C3演算法.
經典類,MRO=深度優先搜尋
經典類是一種不能繼承的類,如果經典類被作為父類,子類呼叫父類的建構函式則會出錯.
經典類的MRO採用 “深度優先搜尋“,子節點順序從左到右;
經典類沒有__mro__屬性;
當出現菱形繼承時,深度優先搜尋就會出現問題.下面給出了正常繼承模式和菱形繼承模式下,採用DFS得出的MRO:
<畫繼承關係圖時,要注意繼承順序,先繼承的在最左側,謹記這點,繼承順序不同,mro也是不一樣的>
正常繼承:
如果兩個互不相關的類多重繼承,此時MRO正常,不會引起任何問題;
菱形繼承:
B和C有公共父類D,假設C重寫了D中的fun()方法,按照MRO順序先找到D中的fun()方法,此時C中重寫的fun()方法將永遠訪問不到,導致了C只能繼承不能重寫D中的方法(即使C重寫了fun()方法也不會訪問到),這就是深度優先搜尋的缺陷;
新式類,MRO = C3
為了使類和內建型別更加統一,解決經典類中多重繼承只能繼承不能重寫的問題,引入了新式類.新式類的每個類都繼承於一個基類(object),可以是自定義類或其他類,預設繼承於object.子類可以呼叫父類的建構函式.
#經典類
class A():
pass
#新式類
class A(object):
pass
1
2
3
4
5
6
新式類採用C3演算法求解mro,並且新式類是有__mro__屬性的.
BFS,廣度優先搜尋
在介紹C3演算法時,有必要了解BFS.如果新式類的MRO採用BFS(廣度優先搜尋,子節點順序從左到右)
當出現正常繼承時,廣度優先搜尋就會出現問題.下面給出了正常繼承模式和菱形繼承模式下,採用BFS得出的MRO:
<畫繼承關係圖時,要注意繼承順序,先繼承的在最左側,謹記這點,繼承順序不同,mro也是不一樣的>
正常繼承:
如果兩個互不相關的類多重繼承,則會發生問題.
假設:D中有foo()方法,C也有foo()方法,則按照MRO順序,應該是先在B中查詢,再在C中查詢.
正常情況下,如果在B中未找到的話,應該在B的父類中查詢,而不是在C中查詢.
這種先在B中查詢然後再D中查詢的順序,稱為單調性.(這就像繼承遺產一樣,如果繼承人的兒子媳婦不在了,要先找繼承人的孫子,孫女啊,而不是找什麼侄子,外甥什麼的…..)
菱形繼承:
解決了深度優先搜尋出現的只能繼承不能重寫問題,但是違反了繼承的單調性原則.
C3演算法
下面開始正題,從Python2.3開始,新式類的MRO採用C3演算法,解決了單調性和只能繼承無法重寫的問題.
MRO採用C3演算法後,此時上述兩種繼承模式的MRO:
程式碼實現:
#!/usr/bin/python
# -*- coding: utf-8 -*-
#正常繼承.py
class D(object):
def __init__(self):
super(D,self).__init__()
print 'D'
def fun(self):
print 'D fun()'
class E(object):
def __init__(self):
super(E,self).__init__()
print 'E'
class C(E):
def __init__(self):
super(C,self).__init__()
print 'C'
def fun(self):
print 'C fun()'
class B(D):
def __init__(self):
super(B,self).__init__()
print 'B'
class A(B,C):
def __init__(self):
super(A,self).__init__()
print 'A'
if __name__ == '__main__':#主程式
a = A() #E C D B A
print A.__mro__#(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.D'>, <class '__main__.C'>, <class '__main__.E'>, <type 'object'>) 正確
a.fun() #D fun() 按照MRO搜尋fun()方法,在D中找到
#菱形繼承.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
class D(object):
def __init__(self):
super(D,self).__init__()
print 'D'
def fun(self):
print 'D fun()'
class C(D):
def __init__(self):
super(C,self).__init__()
print 'C'
def fun(self):
print 'C fun()'
class B(D):
def __init__(self):
super(B,self).__init__()
print 'B'
class A(B,C):
def __init__(self):
super(A,self).__init__()
print 'A'
if __name__ == '__main__':#主程式
a = A() #DCBA
print A.__mro__ #(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <type 'object'>) 正確
a.fun() #C fun() 按照MRO搜尋fun()方法,在C中找到
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
C3演算法成功解決了單調性和只能繼承無法重寫的問題.下面來看下神奇的C3演算法是如何計算mro的.
C3演算法計算訪問順序列表
merge list公式法
C3演算法的核心就是merge.
在merge列表中,如果第一個序列mro的第一個類出現在其它序列,並且也是第一個,或者某個類僅在一個序列中存在,其它序列不存在,那麼這個類就會從這些序列中刪除,併合到訪問順序列表中,如果在第一個序列中未找到滿足上述條件的類,則在第二個序列開始查詢,如果在第二個中也找不到符合條件的,則在第三個序列中查詢……
謹記:mro(object) = [O]
如:class B(A1,A2,A3 …) 繼承順序也很重要,不要忽略了
此時B的mro序列 mro(B) = [B] + merge(mro(A1), mro(A2), mro(A3) …, [A1,A2,A3])
merge中的排列要嚴格遵守繼承順序.
以正常繼承模式的幾個類為例:(O為object類,新式類為object的子類)
mro(D) = [D,O]
mro(E) = [E,O]
mro(B) = [B] + merge(mro(D) ,[D]) = [B] + merge([D,O] + [D]) = [B,D] +merge([O]) = [B,D,O]
同理:mro(C) = [C,E,O]
mro(A) = [A] + merge(mro(B),mro(C),[B,C])
= [A] + merge([B,D,O],[C,E,O],[B,C]) #B為第一個序列的第一個類,並且B在第三個序列中也是第一個類,合併
= [A,B] + merge([D,O],[C,E,O],[C]) #D僅在第一個序列中存在,合併
= [A,B,D] + merge([O],[C,E,O],[C]) #第一個序列中O不滿足任何條件,在第二個序列中查詢,C為第二個序
的第一個類,並且也為第三個序列的第一個類,合併
= [A,B,D,C] + merge([O],[E,O]) #第一個序列中的O不滿足任何條件,在第二個序列中查詢,E僅在第二
個序列中存在,合併
= [A,B,D,C,E] + merge([O],[O]) #O為第一個序列的第一個類,也是第二個序列的第一個類,合併
= [A,B,D,C,E,O]
result: [A,B,D,C,E,O] 正確
拓撲排序求解mro
解決單調性(BFS正常繼承出現的問題):只要保證從根到葉,從左到右的訪問順序即可;
只能繼承,不能重寫(DFS菱形繼承出現的問題):主要是因為先訪問了子類的父類導致的.
如果熟悉圖論的話,應該能想到拓撲排序能很好的解決上述問題.
拓撲排序:
對於一個有向無環圖G進行拓撲排序,就是將G中所有的頂點排成一個線性序列,使得圖中任意一對頂點u和v,若邊(u,v)屬於E(G),則u線上性序列中出現在v之前.通常,這樣的線性序列稱之為滿足拓撲次序的序列,簡稱拓撲排序.
因為拓撲排序肯定是由根節點到葉節點,所以只要滿足從左到右的原則,得到的拓撲排序就是mro.
以正常繼承模式和菱形繼承模式的幾個類為例,用拓撲排序求解mro:
找到入度為0的點,只有A,將A加入mro順序列表中,剪掉與A相連的邊;
再找入度為0的點,有兩個點B和C,取最左,so將B加入mro順序列表中,剪掉與B相連的邊;
再找入度為0的點,有兩個點D和C,取最左,so將D加入mro順序列表中,剪掉與D相連的邊;
再找入度為0的點,只有C,so將C加入mro順序列表中,剪掉與C相連的邊;
再找入度為0的點,只有E,so將E加入mro順序列表中;
result: mro = [A,B,D,C,E] 正確
菱形繼承:
找到入度為0的點,只有A,將A加入mro順序列表中,剪掉與A相連的邊;
再找入度為0的點,有兩個點B和C,取最左,so將B加入mro順序列表中,剪掉與B相連的邊;
再找入度為0的點,只有C,so將C加入mro順序列表中,剪掉與C相連的邊;
再找入度為0的點,只有D,so將D加入mro順序列表中;
result: mro = [A,B,C,D] 正確
比較BFS,DFS,C3求解mro
演算法 正常繼承 mro順序 菱形繼承 mro順序
DFS A,B,D,C,E A,B,D,C (只能繼承,不能重寫問題)
BFS A,B,C,D,E(不滿足單調性原則) A,B,C,D
C3 A,B,D,C,E A,B,C,D
實戰計算mro
#!/usr/bin/python
# -*- coding: utf-8 -*-
class E(object):
pass
class D(object):
pass
class F(object):
pass
class C(D,F):
pass
class B(E,D):
pass
class A(B,C):
pass
if __name__ == '__main__':
print A.__mro__#(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.F'>, <type 'object'>)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
上述程式碼的繼承關係圖,繼承順序為從左到右:
利用merge計算A的mro:(O代表object類)
mro(E) = [E,O]
mro(D) = [D,O]
mro(F) = [F,O]
mro(B) = [B] + merge(mro(E),mro(D),[E,D])
= [B] + merge([E,O],[D,O],[E,D]) E符合merge條件
= [B,E] + merge([O],[D,O],[D]) D符合merge條件
= [B,E,D] + merge([O],[O],[]) O符合merge條件
= [B,E,D,O]
mro(C) = [C] + merge(mro(D),mro(F),[D,F])
= [C] + merge([D,O],[F,O],[D,F]) D符合merge條件
= [C,D] + merge([O],[F,O],[F]) F符合merge條件
= [C,D,F] + merge([O],[O],[]) O符合merge條件
= [C,D,F,O]
mro(A) = [A] + merge(mro(B),mro(C),[B,C])
= [A] + merge([B,E,D,O] ,[C,D,F,O] ,[B,C]) B符合merge條件
= [A,B] + merge([E,D,O] ,[C,D,F,O] ,[C]) E符合merge條件
= [A,B,E] + merge([D,O] ,[C,D,F,O] ,[C]) C符合merge條件
= [A,B,E,C] + merge([D,O] ,[D,F,O] ,[]) D符合merge條件
= [A,B,E,C,D] + merge([O] ,[F,O] ,[]) F符合merge條件
= [A,B,E,C,D,F] + merge([O] ,[O] ,[]) O符合merge條件
= [A,B,E,C,D,F,O] 與程式結果一致
利用拓撲排序計算mro
根據繼承關係圖,(簡單來說就是不停找入度為0的點,然後減去相連的邊,不停重複,直至只剩下一個點)
找到入度為0的點,只有A,將A加入mro順序列表中,剪掉與A相連的邊;
再找入度為0的點,有兩個點B和C,取最左,so將B加入mro順序列表中,剪掉與B相連的邊;
再找入度為0的點,有兩個點E和C,取最左,so將E加入mro順序列表中,剪掉與E相連的邊;
再找入度為0的點,只有C,so將C加入mro順序列表中,剪掉與C相連的邊;
再找入度為0的點,有兩個點D和F,取最左,so將D加入mro順序列表中,剪掉與D相連的邊;
再找入度為0的點,只有F,so將F加入mro順序列表中,剪掉與F相連的邊;
只剩下object,將object加入mro順序列表中;
此時得出mro = [A,B,E,C,D,F,O],與程式結果一致.
總結
mro的計算不必深究,瞭解就好,在實際應用時,直接使用class.__mro__檢視方法解析順序即可.另外在開發中要儘量避免多重繼承,不要混用經典類和新式類……
---------------------
作者:憂桑的小兔子
來源:CSDN
原文:https://blog.csdn.net/lis_12/article/details/52859376
版權宣告:本文為博主原創文章,轉載請附上博文連結!