python學習筆記6.4-類的多重繼承(super()函式)
說到面向物件,就少不了研究面向物件的特點(繼承,封裝,多型)。Python中類的繼承的關鍵是正確使用super()函式,而這恰好是我們理解最不好的地方。先看看一般類的繼承的程式碼(關於我寫的類的詳解1就是這麼寫,現在覺得寫法實在比較粗糙):
class Base:
def __init__(self):
print('This is Base init function')
class A(Base):
def __init__(self):
Base.__init__(self)
print('This is init function of A' )
class B(Base):
def __init__(self):
Base.__init__(self)
print('This is init function of B')
class C(A,B):
def __init__(self):
#super().__init__(self)
A.__init__(self)
B.__init__(self)
print('This is init function of C')
c = C()
控制檯輸出:
This is Base init function
This is init function of A
This is Base init function
This is init function of B
This is init function of C
從列印輸出來看,初始化確實也沒有什麼問題。只是Base()被初始化兩次,並不影響其功能,只是程式設計師接受不了。接下來用super()函式重寫該段程式碼:
class Base:
def __init__(self):
print('This is Base init function')
class A(Base) :
def __init__(self):
super().__init__()
print('This is init function of A')
class B(Base):
def __init__(self):
super().__init__()
print('This is init function of B')
class C(A,B):
def __init__(self):
super().__init__()
#A.__init__(self)
#B.__init__(self)
print('This is init function of C')
c = C()
列印輸出:
This is Base init function
This is init function of B
This is init function of A
This is init function of C
用super()函式重寫之後,Base的初始化函式就只運行了一次。
要理解為什麼會這樣,我們得先去理解python是如何實現類的繼承的。針對於每一個定義的類,python都會計算出一個方法解析順序(MRO)的列表。MRO列表只是簡單地對所有的基類進行線性排列:
print(C.__mro__)
print(type(C.__mro__))
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.Base'>, <class 'object'>)
<class 'tuple'>
可以看出,MRO是以元組的結構來儲存的,同時,要實現繼承,python從MRO最左邊的類開始(子類),從左到右依次查詢,直到查詢到待查的屬性為止。
MRO列表是如何確定的呢,python採用的是C3線性化處理(C3 linearization)的技術。簡單來說就是一種針對父類的歸併排序它滿足3個條件:
(1)先檢查子類,再檢查父類
(2)有多個父類(多重繼承),按照MRO列表的順序依次檢查
(3)如果下一個類中出現兩個合法的選擇,那麼就從第一個父類中選擇(避免重複繼承,保證每個父類只繼承一次)
當使用super()函式時,python會繼續從MRO中的下一個類開始搜尋,只要每一個重新定義過的方法(比如init())都使用了super()函式,並且呼叫了他們一次,那麼控制流最終就可以遍歷整個MRO列表,並且讓每個方法都只被呼叫一次(這就是第二個例子中為什麼Base.init()只被呼叫一次的原因)。
關於super()函式,還有一個很神奇的功能:它並不是一定要關聯到某個類的直接父類上,甚至可以在沒有直接父類的類上使用它。例如:
class A:
def __init__(self):
print('This is init function of A')
class B:
def __init__(self):
print('This is init function of B')
super().__init__()
class C(B,A):
pass
c = C()
列印輸出:
This is init function of B
This is init function of A
那麼問題來了,A並不是B的父類,但是B中使用super函式仍然可以呼叫A中的init()。這個就可以利用C的MRO列表來解釋:
(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
B中使用了super()它就會遍歷MRO,尋找下一個方法,在A中找到了,所以就呼叫了它。
這就說明,在使用super()函式時可能會產生我們並不想要的結果,所以我們要遵守一些基本規則來避免這些情況的發生。例如:確保在繼承體系中所有同名的方法都有可相容的呼叫簽名(引數數量相同,引數名稱也相同,則只會呼叫一個)