本文爲第二篇讀者投稿 ,歡迎新老朋友有償投稿哦 。
什麼是多重繼承
繼承是面向對象編程的一個重要的方式 ,通過繼承 ,子類就可以擴展父類的功能 。和 c++ 一樣 ,在 python 中一個類能繼承自不止一個父類 ,這叫做 python 的多重繼承(Multiple Inheritance )。多重繼承的語法與單繼承類似 。
class SubclassName(BaseClass1, BaseClass2, BaseClass3, ...): pass
當然 ,子類所繼承的所有父類同樣也能有自己的父類 ,這樣就可以得到一個繼承關係機構圖如下圖所示 :
鑽石繼承(菱形繼承)問題
多重繼承容易導致鑽石繼承(菱形繼承)問題 ,關於爲什麼會叫做鑽石繼承問題 ,看下圖就知道了 :
在 python 中 ,鑽石繼承首先體現在父類方法的調用順序上 ,比如若B和C同時重寫了 A 中的某個方法時 :
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
如果我們實例化 D 爲 d ,然後調用 d.m() 時 ,會輸出 "m of B called",如果 B 沒有重寫 A 類中的 m 方法時 :
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
此時調用 d.m 時,則會輸出 "m of C called" , 那麼如何確定父類方法的調用順序呢 ,這一切的根源還要講到方法解析順序(Method Resolution Order,MRO),這一點我們等會再將。
鑽石繼承還有一個問題是 ,比如若 B 和 C 中的 m 方法也同時調用了 A 中的m方法時 :
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)
此時我們調用 d.m ,A.m 則會執行兩次。
m of D called m of B called m of A called m of C called m of A called
這種問題最常見於當我們初始化 D 類時 ,那麼如何才能避免鑽石繼承問題呢 ?
super and MRO
其實上面兩個問題的根源都跟 MRO 有關 ,MRO(Method Resolution Order) 也叫方法解析順序 ,主要用於在多重繼承時判斷調的屬性來自於哪個類 ,其使用了一種叫做 C3 的算法 ,其基本思想時在避免同一類被調用多次的前提下 ,使用廣度優先和從左到右的原則去尋找需要的屬性和方法 。當然感興趣的同學可以移步 :MRO介紹 。
比如針對如下的代碼 :
>>> 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
當你打印 A.__mro__ 時可以看到輸出結果爲 :
(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.E'>, <class '__main__.F'>, <class 'object'>)
如果我們實例化 A 爲 a 並調用 a.m() 時 ,如果 A 中沒有 m 方法 ,此時python 會沿着 MRO 表逐漸查找 ,直到在某個父類中找到m方法並執行 。
那麼如何避免頂層父類中的某個方法被執行多次呢 ,此時就需要super()來發揮作用了 ,super 本質上是一個類 ,內部記錄着 MRO 信息 ,由於 C3 算法確保同一個類只會被搜尋一次 ,這樣就避免了頂層父類中的方法被多次執行了 ,比如針對鑽石繼承問題 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()
此時打印的結果就變成了 :
m of D called m of B called m of C called m of A called
結論
多重繼承問題是個坑 ,很多編程語言中並沒有多重繼承的概念 ,畢竟它帶來的麻煩比能解決的問題都要多 ,所以如果不是特別必要 ,還是儘量少用多重繼承 。如果你非要用 ,那你要好好研究下類的層次結構 ,至少要對 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