python | 關於多重繼承那些事

本文爲第二篇讀者投稿 ,歡迎新老朋友有償投稿哦 。

什麼是多重繼承

繼承是面向對象編程的一個重要的方式 ,通過繼承 ,子類就可以擴展父類的功能 。和 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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章