一文講解python中的繼承沖突及繼承順序
簡單的菱形繼承
設計類如下
設計代碼:
class Animal(object): def __init__(self, age: int = None, gender: int = None) -> None: print("Call the constructor of Animal.") self.m_age = age self.m_gender = gender self.m_name = "Animal" print("Ends the call to Animal's constructer.") pass def eat(self): print("Animal is eating") def sleep(self): print("Animal is sleeping") class Tiger(Animal): def __init__(self, age: int = None, gender: int = None) -> None: print("Call the constructor of Tiger.") self.m_name = "Tiger" super().__init__(age, gender) print("Ends the call to Tiger's constructer.") def eat(self): print("Tiger is eating") pass class Lion(Animal): def __init__(self, age: int = None, gender: int = None) -> None: print("Call the constructor of Lion.") self.m_name = "Lion" super().__init__(age, gender) print("Ends the call to Lion's constructer.") def eat(self): print("Lion is eating") def sleep(self): print("Lion is sleeping") pass class Liger(Tiger, Lion): def __init__(self, age: int = None, gender: int = None) -> None: super().__init__(age, gender) pass if __name__ == '__main__': liger = Liger(8, 1) #實例化一個`Liger` print(Liger.__mro__) print(liger.m_name) liger.eat() liger.sleep()
運行輸出為:
Call the constructor of Tiger.
Call the constructor of Lion.
Call the constructor of Animal.
Ends the call to Animal's constructer.
Ends the call to Lion's constructer.
Ends the call to Tiger's constructer.
(<class '__main__.Liger'>, <class '__main__.Tiger'>, <class '__main__.Lion'>, <class '__main__.Animal'>, <class 'object'>)
Animal
Tiger is eating
Lion is sleeping
- 繼承順序:通過構造函數的打印順序和
Liger
的mro
(顯示繼承順序)可以看到,繼承順序為Liger -> Tiger -> Lion -> Animal
。多繼承時,繼承順序一般為從左到右,從下到上。 - 同名變量:
Animal
,Tiger
,Lion
的初始化函數中都初始化了m_name
變量,卻并沒有同C++
中的繼承沖突一樣出現(xiàn)多份m_name
的拷貝,而是只對Liger
的實例liger
進行動態(tài)地修改同一塊內存地址,由于Animal
的初始化函數最后被調用,所以m_name
賦值為Animal
。 - 同名函數:
- 對于
Liger
的兩父類Tiger
,Lion
和其祖先Animal
都定義過的函數eat
,Liger
類總是選擇最左父類的同名函數,所以調用Liger.eat()
得到的是Tiger.eat()
。 - 而如果并不是所有父類都定義過的函數,子類在類型樹上從左到右尋找第一個定義過該函數的父類。例如
Liger.sleep()
,Tiger
中并未定義sleep()
,所以找到了后面的Lion.sleep()
。
- 對于
小結:對于簡單的菱形繼承,可以大致認為其是在類型樹上按照"從左到右,從上到下"的廣度優(yōu)先遍歷順序查找成員的。
復雜的菱形繼承
對于復雜的菱形繼承,有時候按照上面的廣度優(yōu)先遍歷類型樹得到的繼承順序并不正確。例如:
這時如果使用廣度優(yōu)先遍歷得到的繼承順序為:M A B Z X Y object
。
運行如下Python
代碼得到的繼承順序為:
class X(object): pass class Y(object): pass class Z(object): pass class A(X, Y): pass class B(Y, Z): pass class M(A, B, Z): pass print(M.mro())
# [<class '__main__.M'>,
<class '__main__.A'>,
<class '__main__.X'>,
<class '__main__.B'>,
<class '__main__.Y'>,
<class '__main__.Z'>,
<class 'object'>]
繼承順序為M A X B Y Z object
。
查閱資料得知,這是因為在Python2.3
以后的版本,類的繼承順序求法采用了C3
算法,以保證繼承的單調性原則。(子類不能改變基類的MRO
搜索順序)
MRO C3 算法
算法原理
C3(C3 linearization)算法實現(xiàn)保證了三種重要特性:
- 繼承拓撲圖的一致性。
- 局部優(yōu)先原則。
- 單調性原則。
在C3算法中,把L[C]定義為類C的的linearization值(也就是MRO里的繼承順序,后面簡稱L值),計算邏輯如下:
L[C] = C + merge of linearization of parents of C and list of parents of C in the order they are inherited from left to right.
即L[C]
是所有父類的L值的merge
。
運算規(guī)則為:
其中 C 多繼承父類 B 1 . . B N
merge的運算方法如下:
- 對于
merge
的參數列表,從左到右檢查每個參數的第一個元素,記為H
。 - 如果
H
不出現(xiàn)在其它參數中,或者出現(xiàn)在某個參數中且是該參數第一個元素(頭),則從所有列表中刪去H
并添加到C
的后面形成C1
。(即H
不被C
的所有父類繼承,以保證L值的單調性)
重復上述步驟直至列表為空或者不能找出可以輸出的元素。
算法例子
拿上面的繼承來舉例:
從上至下一次計算object,X,Y,Z,A,B,M
的繼承順序:
L[object] = O(object)
。
L[X] = X + merge(L[object]) = X + O = XO
,同理L[Y] = YO
,L[Z] = ZO
。
L[A] = A+merge(L[X],L[Y],XY) = A + merge(XO,YO,XY) = AX + merge(O,YO,Y) = AXY + merge(O,O) = AXYO L[B] = B + merge(L[Y],L[Z],YZ) = B + merge(YO,ZO,YZ) = BY + merge(O,ZO,Z) = BYZ + merge(O,O) = BYZO
然后是M
的繼承順序計算:
L[M] = M + merge(L[A],L[B],L[Z],ABZ) = M + merge(AXYO,BYZO,ZO,ABZ) = MA + merge(XYO,BYZO,ZO,BZ) = MAX + merge(YO,BYZO,ZO,BZ) #第一個參數中Y被第二個參數中的Z繼承,所以檢查第二個參數的第一個元素即 B = MAXB + merge(YO,YZO,ZO,Z) = MAXBY + merge(O,ZO,ZO,Z) #同樣,O被Z繼承 = MAXBYZ + merge(O,O,O) = MAXBYZO
得到類M
的最終繼承順序MAXBYZO
。
算法實現(xiàn)
下面是其它資料中找到的Wiki百科上對該算法的Python
版本實現(xiàn):
def c3MRO(cls): if cls is object: # 討論假設頂層基類為object,遞歸終止 return [object] # 構造C3-MRO算法的總式,遞歸開始 mergeList = [c3MRO(baseCls) for baseCls in cls.__bases__] mergeList.append(list(cls.__bases__)) mro = [cls] + merge(mergeList) return mro def merge(inLists): if not inLists: # 若合并的內容為空,返回空list # 配合下文的排除空list操作,遞歸終止 return [] # 遍歷要合并的mro for mroList in inLists: # 取head head = mroList[0] # 遍歷要合并的mro(與外一層相同),檢查尾中是否有head ### 此處也遍歷了被取head的mro,嚴格地來說不符合標準算法實現(xiàn) ### 但按照多繼承中地基礎規(guī)則(一個類只能被繼承一次), ### head不可能在自己地尾中,無影響,若標準實現(xiàn),反而增加開銷 for cmpList in inLists[inLists.index(mroList) + 1:]: if head in cmpList[1:]: break else: # 篩選出好head nextList = [] for mergeItem in inLists: if head in mergeItem: mergeItem.remove(head) if mergeItem: # 排除空list nextList.append(mergeItem) # 遞歸開始 return [head] + merge(nextList) else: # 無好head,引發(fā)類型錯誤 raise TypeError
測試:
class A(object):pass class B(object):pass class C(object):pass class E(A,B):pass class F(B,C):pass class G(E,F):pass print([i.__name__ for i in c3MRO(G)])
輸出結果:
['G', 'E', 'A', 'F', 'B', 'C', 'object']
參考資料
到此這篇關于一文講解python中的繼承沖突及繼承順序的文章就介紹到這了,更多相關python 繼承沖突及繼承順序內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
pytorch+sklearn實現(xiàn)數據加載的流程
這篇文章主要介紹了pytorch+sklearn實現(xiàn)數據加載,之前在訓練網絡的時候加載數據都是稀里糊涂的放進去的,也沒有理清楚里面的流程,今天整理一下,加深理解,也方便以后查閱,需要的朋友可以參考下2022-11-11python利用pandas將excel文件轉換為txt文件的方法
今天小編就為大家分享一篇python利用pandas將excel文件轉換為txt文件的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-10-10python+tkinter實現(xiàn)學生管理系統(tǒng)
這篇文章主要為大家詳細介紹了python+tkinter實現(xiàn)學生管理系統(tǒng),具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-08-08Python數據分析之雙色球基于線性回歸算法預測下期中獎結果示例
這篇文章主要介紹了Python數據分析之雙色球基于線性回歸算法預測下期中獎結果,涉及Python基于線性回歸算法的數值運算相關操作技巧,需要的朋友可以參考下2018-02-02