繼承是面向對象編程的一個重要的方式 ,通過繼承 ,子類就可以擴展父類的功能 。和 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