Python:多重繼承 和 MRO順序(C3算法)


python存在多重繼承機制,但是先說:盡量不要用多重繼承。

有點多,慢慢看。。。

目錄:

1. 講多重繼承前,先看看:每一代都是單繼承的繼承問題

2. 子類訪問父類 —— super方法

3. 多重繼承 --- 非super

4. 多重繼承 --- super

5. MRO順序 --- C3算法

# -------------------------------------------------------------------------------------

  • 講多重繼承前,先看看:每一代都是單繼承的繼承問題
class grandfather(object):
    ''' grandfather類的定義如下'''
    con_flag = 1   #普通靜態字段
    __con_data = '爺爺'  #私有靜態變量只能在類內使用
    def __init__(self,name,age,id_):
        self.name = name
        self.age = age
        self.__id = id_
        print('grandfather類初始化完成')
        
    def __get_age(self):   #私有函數只能在類內使用
        return self.age
    
    def show_age(self):
        print(self.age)
        print('grandfather年齡顯示完畢')

class father(grandfather):
    ''' father類的定義如下'''
    con_flag = 2   #普通靜態字段
    __con_data = '爸爸'  #私有靜態變量只能在類內使用

class son(father):
    ''' son類的定義如下'''
    con_flag = 4   #普通靜態字段
    __con_data = '兒子'  #私有靜態變量只能在類內使用

if __name__ == '__main__':
    son1 = son('Tom',10,'001')  #實例化時,會依次查找上層父類的__init__函數,注意這個函數並不一定要有
    son1.show_age()   #子類中有該方法,則會直接執行;如果沒有才去上層父類,每次單繼承時這個很清晰

可見:

(1)son類繼承自father類,而father類繼承自grandfather類;所以son的實例對象也擁有了grandfather類的方法和屬性;只不過屬性/方法名稱相同時,子類的屬性/方法會覆蓋父類。

(2)當對象調用某方法時,先在子類(本類)中查找,找不到再向上查找父類,一直向上直到找到第一個該方法出現的類。

  • 子類訪問父類 —— super方法

有時子類中重寫了某些方法或者屬性,但是又想要使用父類的方法或者屬性;或者子類單純的想使用父類的方法或屬性,可以用super方法。讓我們稍微改變一下上述代碼:

class grandfather(object):
    ''' grandfather類的定義如下'''
    con_flag = 1   #普通靜態字段
    __con_data = '爺爺'  #私有靜態變量只能在類內使用
    def __init__(self,name,age,id_):
        self.name = name
        self.age = age
        self.__id = id_
        print('grandfather類初始化完成')
        
    def __get_age(self):   #私有函數只能在類內使用
        return self.age
    
    def show_age(self):
        print(self.age)
        print('grandfather年齡顯示完畢')

class father(grandfather):
    ''' father類的定義如下'''
    con_flag = 2   #普通靜態字段
    __con_data = '爸爸'  #私有靜態變量只能在類內使用

class son(father):
    ''' son類的定義如下'''
    con_flag = 4   #普通靜態字段
    __con_data = '兒子'  #私有靜態變量只能在類內使用

    def show_age(self):
      super(son, self).show_age()  # 顯式表示調用某個類的超類,此時father類並沒有show_age方法,所以繼續向上查找超類,找到grandfather的方法
      super().show_age()          # super().show_age() 默認指的是當前類son的超類,用於隱式指代父類,而不用提供父類名稱
      grandfather.show_age(self)  #此時必須提供self參數
     print(super().con_flag) #父類father中有該屬性,於是不會繼續向上查找grandfather類了
     print('son年齡顯示完畢')
if __name__ == '__main__': son1 = son('Tom',10,'001') son1.show_age() #子類中有該方法,則會直接執行;如果沒有才去上層,每次單繼承時這個很清晰 print(son1.con_flag)

可見:

(1)子類中調用父類(多層回溯的父類)方法或屬性的方式有兩種:

第一種是 super().func_name() 或者 super().attr_name:例如super().show_age() 、super().con_flag;super(son, self).show_age() 這種形式可以顯示指示是調用哪個類的超類 —— 可以確保多重繼承時父類的方法只被執行一次。

第二種是 父類名.父類方法(self) 或者 父類名.父類屬性:注意此時方法中self參數是必須的 —— 會導致多重繼承時父類中的方法被多次執行,所以多重繼承時最好用super方式,但是最好不要用多重繼承

(2)搜索方式是:單繼承比較簡單,簡言之就是直接依次向上層父類搜索;這個用C3算法(MRO Method Resolution Order)也可以算,后面介紹該算法

(3)super方法意義:

單繼承:可以不需要父類名稱就可以調用父類方法;因為父類的名稱可能會變化或者調用其他父類。

多重繼承:用於確保各父類只被搜索、調用一次。

(4)super原理

super(class_name, instance),它所做的事情是:

首先,獲取instance的MRO順序表:instance.__class__.mro(),例如上面的 son1.__class__.mro();

其次,查找class_name在當前MRO列表中的index,然后在instance的MRO列表上搜索class_name類的下一個類。

總結就是:super(class_name, instance)用於在 instance 的 MRO 列表上搜索 class_name類 的下一個類。

  • 多重繼承 --- 非super
class grandfather(object):
    ''' grandfather類的定義如下'''
    con_flag = 1   #普通靜態字段
    __con_data = '爺爺'  #私有靜態變量只能在類內使用
    def __init__(self,name,age,id_):
        self.name = name
        self.age = age
        self.__id = id_
        print('grandfather類初始化完成')
    
    def show_age(self):
        print(self.age)
        print('grandfather年齡顯示完畢')

class father(grandfather):
    ''' father類的定義如下'''
    con_flag = 2   #普通靜態字段
    __con_data = '爸爸'  #私有靜態變量只能在類內使用
    
    def show_age(self):
        grandfather.show_age(self)   #調用父類的方法 print('father年齡顯示完畢')

class aunt(grandfather):
    ''' aunt類的定義如下'''
    con_flag = 3   #普通靜態字段
    __con_data = '姑姑'  #私有靜態變量只能在類內使用

    def show_age(self):
        grandfather.show_age(self)   #調用父類的方法 print('aunt年齡顯示完畢')

class son(father,aunt):
    ''' son類的定義如下'''
    con_flag = 4   #普通靜態字段
    __con_data = '兒子'  #私有靜態變量只能在類內使用

    def show_age(self):
        father.show_age(self)   #子類調用父類的方法
        aunt.show_age(self)     #子類調用父類的方法
        print('son年齡顯示完畢')

if __name__ == '__main__':
    son1 = son('Tom',10,'001')
    son1.show_age()   

可見:

(1)子類son調用父類father和aunt的時候,調用了2次基類grandfather的方法(有兩次:grandfather年齡顯示完畢);如果中間的繼承關系更復雜,那么會顯得更難以理解。

(2)這里的調用父類方法的方式是上述第二種,即:父類名.父類方法(self),而不是super那一種。

正因為如此,所以super方法的使用在多重繼承里面更有意義。

  • 多重繼承 --- super
class grandfather(object):
    ''' grandfather類的定義如下'''
    con_flag = 1   #普通靜態字段
    __con_data = '爺爺'  #私有靜態變量只能在類內使用
    def __init__(self,name,age,id_):
        self.name = name
        self.age = age
        self.__id = id_
        print('grandfather類初始化完成')
    
    def show_age(self):
        print(self.age)
        print('grandfather年齡顯示完畢')
        return self.con_flag

class father(grandfather):
    ''' father類的定義如下'''
    con_flag = 2   #普通靜態字段
    __con_data = '爸爸'  #私有靜態變量只能在類內使用

    def show_age(self):
        s1 = super().show_age() 
        print('father年齡顯示完畢')
        return s1

class aunt(grandfather):
    ''' aunt類的定義如下'''
    con_flag = 3   #普通靜態字段
    __con_data = '姑姑'  #私有靜態變量只能在類內使用

    def show_age(self):
        s2 = super().show_age() 
        print('aunt年齡顯示完畢')
        return s2

class son(father,aunt):
    ''' son類的定義如下'''
    __con_data = '兒子'  #私有靜態變量只能在類內使用

    def show_age(self):
        ss = super().show_age() 
        print('son年齡顯示完畢')
        print(ss)

if __name__ == '__main__':
    son1 = son('Tom',10,'001')
    son1.show_age()   #子類中有該方法,則會直接執行;如果沒有才去下一個類查找
    print(son1._son__con_data)   #本身有這個參數,所以不用回溯查找
    print(son1.__class__.mro())  #查看MRO順序

可見:

(1)用super時,基類grandfather類只訪問了一次。

(2)相同屬性名稱時,下層屬性名(方法)會覆蓋下一個類的屬性名(或方法),例如con_flag屬性,通過MRO順序,son1對象獲取的是father類的該屬性con_flag;例如__con_data屬性,在本類(son類)中存在,所以會覆蓋下一個類的屬性。

(3)類的搜索順序是:[<class '__main__.son'>, <class '__main__.father'>, <class '__main__.aunt'>, <class '__main__.grandfather'>, <class 'object'>],這是通過C3算法計算出來的MRO順序列表。

(4)之所以說下一個類而不說父類,是因為這個順序是C3算法計算的,不是嚴格的繼承順序。

(5)由MRO順序,解析一下son實例化對象son1所包含的信息:

(6)依據son1對象的信息,分析son1.show_age()的執行過程:

 

  • MRO順序 --- C3算法

python官網上有詳細解釋,這里稍微展開一下:

1. 首先 (C1C2C3...Cn)表示一個多重繼承序列,注意這個順序很重要

2. 則 head(頭)=C1,tail(尾)=(C2C3...Cn),即除了第一個類屬於head之外,其他的全屬於tail

3. 使用 C+(C1C2C3...Cn) = CC1C2...Cn 表示類序列的和;

4. 那么,類C的線型查找序列公式就是類C加上父類的線型查找序列和父類的線型序列的混合merge,符號表示就是:L[C(C1C2...Cn)] = C + merge(L[C1], ... ,L(Cn), C1...Cn);

5. 如果C沒有父類,則L[C] = C

其規則就是:取第一個類序列的head,例如CC1C2...Cn的head就是C,如果這個head不在任何其他序列的tail里面,就把這個head加入到查找序列里面 —— 認為這是一個好head,並且從merge表達式里面去掉這個類;否則的話,就取第二個類序列,判斷這個類序列的head是否合格,如果是個好head,則同樣加入到查找序列,否則,繼續對下一個類序列判斷;直到所有類class都在merge里面被去掉,也就是全部進入查找序列;如果最后還是存在類class不能進去查找序列,則返回Exception。

示例展示:

首先類的繼承關系如下:

O = object類
class F(O): pass
class E(O): pass
class D(O): pass
class C(D,F): pass
class B(D,E): pass
class A(B,C): pass

 

我們要算的是A的MRO順序就是:

L[A(B(D(O),E(O)),C(D(O),F(O)))] = A + merge(L[B(D(O),E(O))], L[C(D(O),F(O))], B(D(O),E(O)) C(D(O),F(O))),這個就是根據上面的類C的線型查找序列公式

  • 簡化一下就是 L[A] = A + merge(L[B], L[C], BC) 

L[B] = L[B(D(O),E(O))]  = B + merge(L[D], L[E], DE)

L[C] = L[C(D(O),F(O))] = C + merge(L[D], L[F], DF)

  • 然后繼續划分:

L[D] = L[D(O)] = D + merge(L[O], O) ,由於O本身沒有父類,所以L[O] = O,所以 L[D] = D + merge(O, O) = D + O =DO ----> merge(O, O)表達式中,O都在第一個位置,也就是head,所以O可以放入L[D]的查找序列中了

同理:L[E] = L[E(O)] = E + merge(L[O], O) = E + O =EO,L[F] = FO

  • 所以得到:

L[B] = B + merge(DO, EO, DE),由於此時D不是任何merge序列的tail(因為每次先從merge第一個序列開始,這里第一個就是DO序列,而DO序列的head就是D),所以D可以提出來放入B的查找序列中,並在merge中去掉D,即:L[B] = B + D +merge(O, EO, E) ,同理先判斷O,由於O在EO序列的tail,所以跳到下一個序列EO,而EO序列的head是E,此時E不在任何merge序列的tail,所以E可以提出來,得到 L[B] = B + D + E +merge(O, O) = BDEO。

同理,L[C] = C + merge(DO, FO, DF) = C + D + F + O = CDFO

  • 代入L[A]得到:

L[A] = A + merge(BDEO, CDFO, BC) ,先判斷BDEO的head即B,此時DEO為tail,發現B不在任何merge序列的tail,所以提出來得到:L[A] = A + B + merge(DEO, CDFO, C);

判斷DEO的head即D,由於D在CDFO的tail,所以跳過,判斷CDFO的head即C,發現滿足條件,所以提出C,得到:L[A] = A + B + C + merge(DEO, DFO);

同理一次判斷,得到:L[A] = A + B + C + D + merge(EO, FO) = A + B + C + D + E + F + O = ABCDEFO。

  • 所以類A的MRO查找序列就是ABCDEFO。

但是!!!!

MRO序列計算也有得不到想要的結果,也就是返回Exception,原文中有個例子:

>>> O = object
>>> class X(O): pass
>>> class Y(O): pass
>>> class A(X,Y): pass
>>> class B(Y,X): pass
class C(A, B): pass

L[C] = C + merge(L[A], L[B], AB) = C + merge(A + merge(L[X], L[Y], XY), B + merge(L[Y], L[X], YX), AB) = C + merge(A+merge(XO, YO, XY), B+merge(YO, XO, YX), AB) = C + merge(A+XYO, B+YXO)  = C + merge(AXYO, BYXO) = C + A + B + merge(XYO, YXO)

此時,merge(XYO, YXO)中的X和Y既是head又是tail,已經無法優化合並,所以會報錯,Exception。

注意:以上計算中,每個繼承關系中,類的繼承排列順序很重要

 

比較復雜吧。。。所以不必要時最好不要多重繼承;例如可以用在子類中分別實例化父類來作為子類的屬性,這樣就可以不用多繼承來實現調用父類的方法,可參考:https://baijiahao.baidu.com/s?id=1660242196992022960&wfr=spider&for=pc

例如:

# 在son中寫入:可以將實例化對象作為屬性放入需要的位置
self.aunt_ = aunt()
self.father_ = father()

 

##

參考:

https://www.cnblogs.com/szy13037-5/articles/9562639.html

https://www.cnblogs.com/silencestorm/p/8404046.html

https://blog.csdn.net/sdzhr/article/details/81084112

https://blog.51cto.com/freshair/2063290

https://www.jianshu.com/p/e188daac678c

https://www.python.org/download/releases/2.3/mro/

https://blog.csdn.net/jonstank2013/article/details/50754834

https://blog.csdn.net/zzsfqiuyigui/article/details/61672631

https://www.cnblogs.com/szy13037-5/articles/9562639.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM