MRO算法

【前言】python

MRO(Method Resolution Order):方法解析順序
Python語言包含了不少優秀的特性,其中多重繼承就是其中之一,可是多重繼承會引起不少問題,好比二義性,Python中一切皆引用,這使得他不會像C++同樣使用虛基類處理基類對象重複的問題,可是若是父類存在同名函數的時候仍是會產生二義性,Python中處理這種問題的方法就是MRO。算法

【歷史中的MRO】

若是不想了解歷史,只想知道如今的MRO能夠直接看最後的C3算法,不過C3所解決的問題都是歷史遺留問題,瞭解問題,才能解決問題,建議先看歷史中MRO的演化。
Python2.2之前的版本:經典類(classic class)時代
經典類是一種沒有繼承的類,實例類型都是type類型,若是經典類被做爲父類,子類調用父類的構造函數時會出錯。
這時MRO的方法爲DFS(深度優先搜索(子節點順序:從左到右))。數據結構

Class A:   # 是沒有繼承任何父類的
    def __init__(self):
        print "這是經典類"


inspect.getmro(A)能夠查看經典類的MRO順序ide

import inspect
class D:
    pass

class C(D):
    pass

class B(D):
    pass

class A(B, C):
    pass

if __name__ == '__main__':
    print inspect.getmro(A)
    
>>  (<class __main__.A at 0x10e0e5530>, <class __main__.B at 0x10e0e54c8>, <class __main__.D at 0x10e0e53f8>, <class __main__.C at 0x10e0e5460>)

MRO的DFS順序以下圖:函數

36071EB6-F4FF-46FA-99F2-F3A205FB7353

兩種繼承模式在DFS下的優缺點。
第一種,我稱爲正常繼承模式,兩個互不相關的類的多繼承,這種狀況DFS順序正常,不會引發任何問題;spa

第二種,棱形繼承模式,存在公共父類(D)的多繼承(有種D字一族的感受),這種狀況下DFS一定通過公共父類(D),這時候想一想,若是這個公共父類(D)有一些初始化屬性或者方法,可是子類(C)又重寫了這些屬性或者方法,那麼按照DFS順序一定是會先找到D的屬性或方法,那麼C的屬性或者方法將永遠訪問不到,致使C只能繼承沒法重寫(override)。這也就是爲何新式類不使用DFS的緣由,由於他們都有一個公共的祖先object。code

Python2.2版本:新式類(new-style class)誕生
爲了使類和內置類型更加統一,引入了新式類。新式類的每一個類都繼承於一個基類,能夠是自定義類或者其它類,默認承於object。子類能夠調用父類的構造函數。cdn

這時有兩種MRO的方法
1. 若是是經典類MRO爲DFS(深度優先搜索(子節點順序:從左到右))。
2. 若是是新式類MRO爲BFS(廣度優先搜索(子節點順序:從左到右))。對象

class A(object):   # 繼承於object
    def __init__(self):
        print "這是新式類"
A.__mro__ 能夠查看新式類的順序


MRO的BFS順序以下圖:排序

6C3FAA19-5C47-426D-9AB2-99789FAEBE6D

兩種繼承模式在BFS下的優缺點。
第一種,正常繼承模式,看起來正常,不過實際上感受很彆扭,好比B明明繼承了D的某個屬性(假設爲foo),C中也實現了這個屬性foo,那麼BFS明明先訪問B而後再去訪問C,可是爲何foo這個屬性會是C?這種應該先從B和B的父類開始找的順序,咱們稱之爲單調性。

第二種,棱形繼承模式,這種模式下面,BFS的查找順序雖然解了DFS順序下面的棱形問題,可是它也是違背了查找的單調性。

由於違背了單調性,因此BFS方法只在Python2.2中出現了,在其後版本中用C3算法取代了BFS。

Python2.3到Python2.7:經典類、新式類和平發展
由於以前的BFS存在較大的問題,因此從Python2.3開始新式類的MRO取而代之的是C3算法,咱們能夠知道C3算法確定解決了單調性問題,和只能繼承沒法重寫的問題。C3算法具體實現稍後講解。

MRO的C3算法順序以下圖:看起簡直是DFS和BFS的合體有木有。可是僅僅是看起來像而已。

7CA5DDFC-F5F4-4060-8B9F-21D123AB71E5

Python3到至今:新式類一統江湖
Python3開始就只存在新式類了,採用的MRO也依舊是C3算法。

【神奇的算法C3】

C3算法解決了單調性問題和只能繼承沒法重寫問題,在不少技術文章包括官網中的C3算法,都只有那個merge list的公式法,想看的話網上不少,本身能夠查。可是從公式很難理解到解決這個問題的本質。我通過一番思考後,我講講我所理解的C3算法的本質。若是錯了,但願有人指出來。

假設繼承關係以下(官網的例子):

class D(object):
    pass

class E(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 A(B, C)括號裏面的順序B,C),子類指向父類,構一張圖。
此處輸入圖片的描述

咱們要解決兩個問題:單調性問題和不能重寫的問題。
很容易發現要解決單調性,只要保證從根(A)到葉(object),從左到右的訪問順序便可。
那麼對於只能繼承,不能重寫的問題呢?先分析這個問題的本質緣由,主要是由於先訪問了子類的父類致使的。那麼怎麼解決只能先訪問子類再訪問父類的問題呢?若是熟悉圖論的人應該能立刻想到拓撲排序,這裏引用一下百科的的定義:

對一個有向無環圖(Directed Acyclic Graph簡稱DAG)G進行拓撲排序,是將G中全部頂點排成一個線性序列,使得圖中任意一對頂點u和v,若邊(u,v)∈E(G),則u在線性序列中出如今v以前。一般,這樣的線性序列稱爲知足拓撲次序(Topological Order)的序列,簡稱拓撲序列。簡單的說,由某個集合上的一個偏序獲得該集合上的一個全序,這個操做稱之爲拓撲排序。

由於拓撲排序確定是根到葉(也不能說是葉了,由於已經不是樹了),因此只要知足從左到右,獲得的拓撲排序就是結果,關於拓撲排序算法,大學的數據結構有教,這裏不作講解,不懂的能夠自行谷歌或者翻一下書,建議瞭解完算法再往下看。

那麼模擬一下例子的拓撲排序:首先找入度爲0的點,只有一個A,把A拿出來,把A相關的邊剪掉,再找下一個入度爲0的點,有兩個點(B,C),取最左原則,拿B,這是排序是AB,而後剪B相關的邊,這時候入度爲0的點有E和C,取最左。這時候排序爲ABE,接着剪E相關的邊,這時只有一個點入度爲0,那就是C,取C,順序爲ABEC。剪C的邊獲得兩個入度爲0的點(DF),取最左D,順序爲ABECD,而後剪D相關的邊,那麼下一個入度爲0的就是F,而後是object。那麼最後的排序就爲ABECDFobject。

對比一下 A.__mro__的結果

(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.F'>, <type 'object'>)
相關文章
相關標籤/搜索