多重繼承的陷阱:鑽石繼承(菱形繼承)問題
阿新 • • 發佈:2019-02-20
支援多繼承的面向物件程式設計都可能會導致鑽石繼承(菱形繼承)問題,看以下程式碼:
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…