談?wù)?python 面向?qū)ο笾械亩嘀乩^承
什么是多重繼承
繼承是面向?qū)ο缶幊痰囊粋€(gè)重要的方式 ,通過繼承 ,子類就可以擴(kuò)展父類的功能 。和 c++ 一樣 ,在 python 中一個(gè)類能繼承自不止一個(gè)父類 ,這叫做 python 的多重繼承(Multiple Inheritance )。多重繼承的語(yǔ)法與單繼承類似 。
class?SubclassName(BaseClass1,?BaseClass2,?BaseClass3,?...):
????pass
當(dāng)然 ,子類所繼承的所有父類同樣也能有自己的父類 ,這樣就可以得到一個(gè)繼承關(guān)系機(jī)構(gòu)圖如下圖所示 :

鉆石繼承(菱形繼承)問題
多重繼承容易導(dǎo)致鉆石繼承(菱形繼承)問題 ,關(guān)于為什么會(huì)叫做鉆石繼承問題 ,看下圖就知道了 :

在 python 中 ,鉆石繼承首先體現(xiàn)在父類方法的調(diào)用順序上 ,比如若B和C同時(shí)重寫了 A 中的某個(gè)方法時(shí) :
class?A(object):
????def?m(self):
????????print("m?of?A?called")
class?B(A):
????def?m(self):
????????print("m?of?B?called")
class?C(A):
????def?m(self):
????????print("m?of?C?called")
class?D(B,C):
????pass
如果我們實(shí)例化 D 為 d ,然后調(diào)用 d.m() 時(shí) ,會(huì)輸出 "m of B called",如果 B 沒有重寫 A 類中的 m 方法時(shí) :
class?A(object):
????def?m(self):
????????print("m?of?A?called")
class?B(A):
????pass
class?C(A):
????def?m(self):
????????print("m?of?C?called")
class?D(B,C):
????pass
此時(shí)調(diào)用 d.m 時(shí),則會(huì)輸出 "m of C called" , 那么如何確定父類方法的調(diào)用順序呢 ,這一切的根源還要講到方法解析順序(Method Resolution Order,MRO),這一點(diǎn)我們等會(huì)再將。
鉆石繼承還有一個(gè)問題是 ,比如若 B 和 C 中的 m 方法也同時(shí)調(diào)用了 A 中的m方法時(shí) :
class?A:
????def?m(self):
????????print("m?of?A?called")
class?B(A):
????def?m(self):
????????print("m?of?B?called")
????????A.m(self)
class?C(A):
????def?m(self):
????????print("m?of?C?called")
????????A.m(self)
class?D(B,C):
????def?m(self):
????????print("m?of?D?called")
????????B.m(self)
????????C.m(self)
此時(shí)我們調(diào)用 d.m ,A.m 則會(huì)執(zhí)行兩次。
m?of?D?called
m?of?B?called
m?of?A?called
m?of?C?called
m?of?A?called
這種問題最常見于當(dāng)我們初始化 D 類時(shí) ,那么如何才能避免鉆石繼承問題呢 ?
super and MRO
其實(shí)上面兩個(gè)問題的根源都跟 MRO 有關(guān) ,MRO(Method Resolution Order) 也叫方法解析順序 ,主要用于在多重繼承時(shí)判斷調(diào)的屬性來(lái)自于哪個(gè)類 ,其使用了一種叫做 C3 的算法 ,其基本思想時(shí)在避免同一類被調(diào)用多次的前提下 ,使用廣度優(yōu)先和從左到右的原則去尋找需要的屬性和方法 。當(dāng)然感興趣的同學(xué)可以移步 :MRO介紹 。
比如針對(duì)如下的代碼 :
>>>?class?F(object):?pass
>>>?class?E(object):?pass
>>>?class?D(object):?pass
>>>?class?C(D,F):?pass
>>>?class?B(D,E):?pass
>>>?class?A(B,C):?pass
當(dāng)你打印 A.__mro__ 時(shí)可以看到輸出結(jié)果為 :
(<class?'__main__.A'>,?<class?'__main__.B'>,?<class?'__main__.C'>,?<class?'__main__.D'>,?<class?'__main__.E'>,?<class?'__main__.F'>,?<class?'object'>)
如果我們實(shí)例化 A 為 a 并調(diào)用 a.m() 時(shí) ,如果 A 中沒有 m 方法 ,此時(shí)python 會(huì)沿著 MRO 表逐漸查找 ,直到在某個(gè)父類中找到m方法并執(zhí)行 。
那么如何避免頂層父類中的某個(gè)方法被執(zhí)行多次呢 ,此時(shí)就需要super()來(lái)發(fā)揮作用了 ,super 本質(zhì)上是一個(gè)類 ,內(nèi)部記錄著 MRO 信息 ,由于 C3 算法確保同一個(gè)類只會(huì)被搜尋一次 ,這樣就避免了頂層父類中的方法被多次執(zhí)行了 ,比如針對(duì)鉆石繼承問題 2 中的代碼可以改為 :
class?A(object):
????def?m(self):
????????print("m?of?A?called")
class?B(A):
????def?m(self):
????????print("m?of?B?called")
????????super().m()
class?C(A):
????def?m(self):
????????print("m?of?C?called")
????????super().m()
class?D(B,C):
????def?m(self):
????????print("m?of?D?called")
????????super().m()
此時(shí)打印的結(jié)果就變成了 :
m?of?D?called
m?of?B?called
m?of?C?called
m?of?A?called
結(jié)論
多重繼承問題是個(gè)坑 ,很多編程語(yǔ)言中并沒有多重繼承的概念 ,畢竟它帶來(lái)的麻煩比能解決的問題都要多 ,所以如果不是特別必要 ,還是盡量少用多重繼承 。如果你非要用 ,那你要好好研究下類的層次結(jié)構(gòu) ,至少要對(duì) C3 算法具有一定的了解吧 ,比如弄懂下面的代碼哪里出了問題 ?
>>>?F=type('Food',(),{'remember2buy':'spam'})
>>>?E=type('Eggs',(F,),{'remember2buy':'eggs'})
>>>?G=type('GoodFood',(F,E),{})?
#?TypeError:?Cannot?create?a?consistent?method?resolution
#?order?(MRO)?for?bases?Food,?Eggs
_往期文章推薦_【編程課堂】計(jì)數(shù)器Counter
