1. 程式人生 > >多重繼承的陷阱:鑽石繼承(菱形繼承)問題

多重繼承的陷阱:鑽石繼承(菱形繼承)問題

支援多繼承的面向物件程式設計都可能會導致鑽石繼承(菱形繼承)問題,看以下程式碼:

class A():
    def __init__(self):
        print("進入A…")
        print("離開A…")

class B(A):
    def __init__(self):
        print("進入B…")
        A.__init__(self)
        print("離開B…")
        
class C(A):
    def __init__(self):
        print("進入C…")
        A.__init__(self)
        print("離開C…")

class D(B, C):
    def __init__(self):
        print("進入D…")
        B.__init__(self)
        C.__init__(self)
        print("離開D…")

>>> d = D()
進入D…
進入B…
進入A…
離開A…
離開B…
進入C…
進入A…
離開A…
離開C…
離開D…

為什麼叫鑽石繼承(菱形繼承),看下圖就明白名字的由來了:

鑽石繼承(菱形繼承)會帶來什麼問題?

多重繼承容易導致鑽石繼承(菱形繼承)問題,上邊程式碼例項化 D 類後我們發現 A 被前後進入了兩次(有童鞋說兩次就兩次憋,我女朋友還不止呢……)。

這有什麼危害?我舉個例子,假設 A 的初始化方法裡有一個計數器,那這樣 D 一例項化,A 的計數器就跑兩次(如果遭遇多個鑽石結構重疊還要更多),很明顯是不符合程式設計的初衷的(程式應該可控,而不能受到繼承關係影響)。

如何避免鑽石繼承(菱形繼承)問題?

為解決這個問題,Python 使用了一個叫“方法解析順序(Method Resolution Order,MRO)”的東西,還用了一個叫 C3 的演算法。

該演算法相對來說比較複雜(有興趣深入演算法的朋友可以閱讀:

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

當然我這裡願意跟你解釋下 MRO 的順序基本就是:在避免同一類被呼叫多次的前提下,使用廣度優先和從左到右的原則去尋找需要的屬性和方法。

在繼承體系中,C3 演算法確保同一個類只會被搜尋一次。例子中,如果一個屬性或方法在 D 類中沒有被找到,Python 就會搜尋 B 類,然後搜尋 C類,如果都沒有找到,會繼續搜尋 B 的基類 A,如果還是沒有找到,則丟擲“AttributeError”異常。

你可以使用 類名.__mro__ 獲得 MRO 的順序(注:object 是所有類的基類,金字塔的頂端):

>>> D.__mro__
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

其實你大可不必為上邊的內容而煩惱,因為這時候你應該召喚 super 函式大顯神威:

class A():
    def __init__(self):
        print("進入A…")
        print("離開A…")

class B(A):
    def __init__(self):
        print("進入B…")
        super().__init__()
        print("離開B…")
        
class C(A):
    def __init__(self):
        print("進入C…")
        super().__init__()
        print("離開C…")

class D(B, C):
    def __init__(self):
        print("進入D…")
        super().__init__()
        print("離開D…")

>>> d = D()
進入D…
進入B…
進入C…
進入A…
離開A…
離開C…
離開B…
離開D…