1. 程式人生 > >12 Python MRO

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 
版權宣告:本文為博主原創文章,轉載請附上博文連結!