1. 程式人生 > >python學習筆記6.4-類的多重繼承(super()函式)

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()函式時可能會產生我們並不想要的結果,所以我們要遵守一些基本規則來避免這些情況的發生。例如:確保在繼承體系中所有同名的方法都有可相容的呼叫簽名(引數數量相同,引數名稱也相同,則只會呼叫一個)