天天看點

12 Python MROPython MRO(方法解析順序)

Python MRO(方法解析順序)

  • Python MRO方法解析順序
    • MRO Method Resolution Order
    • 經典類MRO深度優先搜尋
    • 新式類MRO C3
      • BFS廣度優先搜尋
      • C3算法
      • C3算法計算通路順序清單
        • merge list公式法
        • 拓撲排序求解mro
      • 比較BFSDFSC3求解mro
    • 實戰計算mro
      • 利用merge計算A的mroO代表object類
      • 利用拓撲排序計算mro
    • 總結
    • 參考網址

轉載請标明出處(http://blog.csdn.net/lis_12/article/details/52859376).

MRO (Method Resolution Order)

Python在多重繼承時,如果父類中存在同名函數會産生二義性,Python中處理這個問題方法就是MRO.

如果對MRO發展史沒興趣的話,直接看C3算法.

經典類,MRO=深度優先搜尋

經典類是一種不能繼承的類,如果經典類被作為父類,子類調用父類的構造函數則會出錯.

經典類的MRO采用 “深度優先搜尋“,子節點順序從左到右;

經典類沒有__mro__屬性;

當出現菱形繼承時,深度優先搜尋就會出現問題.下面給出了正常繼承模式和菱形繼承模式下,采用DFS得出的MRO:

<畫繼承關系圖時,要注意繼承順序,先繼承的在最左側,謹記這點,繼承順序不同,mro也是不一樣的>

12 Python MROPython MRO(方法解析順序)
  1. 正常繼承:

    如果兩個互不相關的類多重繼承,此時MRO正常,不會引起任何問題;

  2. 菱形繼承:

    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
           

新式類采用C3算法求解mro,并且新式類是有__mro__屬性的.

BFS,廣度優先搜尋

在介紹C3算法時,有必要了解BFS.如果新式類的MRO采用BFS(廣度優先搜尋,子節點順序從左到右)

當出現正常繼承時,廣度優先搜尋就會出現問題.下面給出了正常繼承模式和菱形繼承模式下,采用BFS得出的MRO:

<畫繼承關系圖時,要注意繼承順序,先繼承的在最左側,謹記這點,繼承順序不同,mro也是不一樣的>

12 Python MROPython MRO(方法解析順序)
  1. 正常繼承:

    如果兩個互不相關的類多重繼承,則會發生問題.

    假設:D中有foo()方法,C也有foo()方法,則按照MRO順序,應該是先在B中查找,再在C中查找.

    正常情況下,如果在B中未找到的話,應該在B的父類中查找,而不是在C中查找.

    這種先在B中查找然後再D中查找的順序,稱為單調性.(這就像繼承遺産一樣,如果繼承人的兒子媳婦不在了,要先找繼承人的孫子,孫女啊,而不是找什麼侄子,外甥什麼的…..)

  2. 菱形繼承:

    解決了深度優先搜尋出現的隻能繼承不能重寫問題,但是違反了繼承的單調性原則.

C3算法

下面開始正題,從Python2.3開始,新式類的MRO采用C3算法,解決了單調性和隻能繼承無法重寫的問題.

MRO采用C3算法後,此時上述兩種繼承模式的MRO:

12 Python MROPython 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中找到
           

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的子類)

12 Python MROPython MRO(方法解析順序)

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:

12 Python MROPython MRO(方法解析順序)
  1. 找到入度為0的點,隻有A,将A加入mro順序清單中,剪掉與A相連的邊;
  2. 再找入度為0的點,有兩個點B和C,取最左,so将B加入mro順序清單中,剪掉與B相連的邊;
  3. 再找入度為0的點,有兩個點D和C,取最左,so将D加入mro順序清單中,剪掉與D相連的邊;
  4. 再找入度為0的點,隻有C,so将C加入mro順序清單中,剪掉與C相連的邊;
  5. 再找入度為0的點,隻有E,so将E加入mro順序清單中;

result: mro = [A,B,D,C,E] 正确

菱形繼承:

12 Python MROPython MRO(方法解析順序)
  1. 找到入度為0的點,隻有A,将A加入mro順序清單中,剪掉與A相連的邊;
  2. 再找入度為0的點,有兩個點B和C,取最左,so将B加入mro順序清單中,剪掉與B相連的邊;
  3. 再找入度為0的點,隻有C,so将C加入mro順序清單中,剪掉與C相連的邊;
  4. 再找入度為0的點,隻有D,so将D加入mro順序清單中;

result: mro = [A,B,C,D] 正确

比較BFS,DFS,C3求解mro

12 Python MROPython 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'>)
           

上述代碼的繼承關系圖,繼承順序為從左到右:

12 Python MROPython MRO(方法解析順序)

利用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的點,然後減去相連的邊,不停重複,直至隻剩下一個點)

  1. 找到入度為0的點,隻有A,将A加入mro順序清單中,剪掉與A相連的邊;
  2. 再找入度為0的點,有兩個點B和C,取最左,so将B加入mro順序清單中,剪掉與B相連的邊;
  3. 再找入度為0的點,有兩個點E和C,取最左,so将E加入mro順序清單中,剪掉與E相連的邊;
  4. 再找入度為0的點,隻有C,so将C加入mro順序清單中,剪掉與C相連的邊;
  5. 再找入度為0的點,有兩個點D和F,取最左,so将D加入mro順序清單中,剪掉與D相連的邊;
  6. 再找入度為0的點,隻有F,so将F加入mro順序清單中,剪掉與F相連的邊;
  7. 隻剩下object,将object加入mro順序清單中;

此時得出mro = [A,B,E,C,D,F,O],與程式結果一緻.

總結

mro的計算不必深究,了解就好,在實際應用時,直接使用class.__mro__檢視方法解析順序即可.另外在開發中要盡量避免多重繼承,不要混用經典類和新式類……

參考網址

  1. (http://xymlife.com/2016/05/22/python_mro/)
  2. (https://segmentfault.com/a/1190000000749061)
  3. (http://www.cnblogs.com/lovemo1314/archive/2011/05/03/2035005.html)
  4. (http://www.cnblogs.com/i2u9/archive/2013/03/19/pythonmroc3.html)​