理解了python的MRO之后,我們就可以更加准確地使用super()函數,以及使用super()完成多繼承協同任務
super().method()是調用父類中的方法,這個搜索順序當然是按照MRO從前向后開始進行的
super([type][, object-or-type])
根據官方文檔,super
函數返回一個委托類type
的父類或者兄弟類方法調用的代理對象。super
函數用來調用已經再子類中重寫過的父類方法。
這句話其實很難看明白,為什么除了父類還可能是兄弟類?
要理解這句話,先談談super
的參數的傳入方式不同帶來的不同之處
常見的是直接調用super()
,這其實是super(type, obj)
的簡寫方式,將當前的類傳入type
參數,同時將實例對象傳入type-or-object
參數,這兩個實參必須確保isinstance(obj, type)
為True
。
使用該方法調用的super
函數返回的代理類是obj
所屬類的MRO中,排在type之后的下一個父類。
示例:
類的繼承結構如下
class A: pass
class B: pass
class C(A,B): pass
類C的MRO為[C, A, B, object]
現在我們為其添加一個方法x()
class A:
def x(self):
print('run A.x')
super().x()
print(self)
class B:
def x(self):
print('run B.x')
print(self)
class C(A,B):
def x(self):
print('run C.x')
super().x()
print(self)
C().x()
該方法最先出現是作為C的實例方法,根據MRO,我們很清楚,下一步它會調用其MRO父類中的同名方法,即A中的x()方法,但是,我們在A的x()方法中再次使用了super(),這時候會怎么樣呢?
查看結果輸出
run C.x
run A.x
run B.x
<__main__.C object at 0x000002B5041BB710>
<__main__.C object at 0x000002B5041BB710>
<__main__.C object at 0x000002B5041BB710>
在調用了A中的x()
方法之后,下一個調用的是B中的x()
方法,在繼承結構中,類A和類B互為兄弟關系,super()
在A中調用的時候,最終卻調用其兄弟的同名方法,這就是之前說的,super
函數返回一個委托類type
的父類或者兄弟類方法調用的代理對象。
那么,為什么?
根據print(self)的輸出,所有在這些super()的調用過程中,self參數傳入的是同一個obj,就是我們初始化的C(), 在內存中位置為0x000002B5041BB710的實例對象。
之前已經說過,super()
是super(type, obj)
的簡寫,在調用super()
時,type
參數傳入的是當前的類,而obj
參數則是默認傳入當前的實例對象,在super()
的后續調用中,obj
一直未變,而實際傳入的class
是動態變化,不過,在首次調用時,MRO就已經被確定,是obj
所屬類(即C)的MRO,因此class
參數的作用就是從已確定的MRO中找到位於其后緊鄰的類,作為再次調用super()
時查找該方法的下一個類。
即,super
函數這一部分的核心邏輯應該為
def super(class, obj):
mro_list = obj.__class__.mro()
next_parent_class = mro_list[mro_list.index(class)+1]
return next_parent_class
這就是為什么必須保證isinstance(obj, type)
為True
的原因,如果不是,那么可能type
就不存在於obj.__class__
的MRO列表中,該算法就無法正確找到下一個應當被查找的類。
因此,如果我們在某個類的父類中按照其MRO順序,每個父類都寫一個同名方法,同時每個該方法中都繼續調用super()
,直到在MRO列表object
之前的最后一個類的同名方法中不再調用super()
,那么在調用該方法時,會在各個父類中按照MRO列表的順序依次被調用,這個過程中存在數據的傳遞,代表它們之間可以共享某些數據,這就實現了多繼承協同工作。
而這種工作方式,通過重寫方法是根本無法實現的。
使用實例:
繼承結構如下圖
我們試圖達到的目的如下:
一個類Final
繼承Header
以獲得屬性header
同時我們通過混合其他類來快捷地修飾header
屬性,例如繼承類Mixin1
會為header
屬性(其數據類型為列表)追加數據data1
,而繼承類Minix2
則會為header
屬性的頭部添加元素data2
,注意,因為這些操作並不沖突,這些行為都不該相互覆蓋。
class Minix1:
"""該混合類為header列表末尾添加data1"""
def get_header(self):
print('run Minix1.get_header')
ctx = super().get_header()
ctx.append('data1')
return ctx
class Minix2:
"""該混合類為header列表頭部添加data2"""
def get_header(self):
print('run Minix2.get_header')
ctx = super().get_header()
ctx.insert(0, 'data2')
return ctx
class Header:
header = []
def get_header(self):
print('run Headers.get_header')
return self.header if self.header else []
class Final(Minix1, Minix2, Header):
def get_header(self):
return super().get_header()
當然,我們可以定義更多的混合類,並從中選取所需的類來快速得到想要的header
屬性, 在這個例子中,這兩個混合類已經足夠說明問題。
我們現在使用類C的get_header()
方法來得到其header
屬性
print(Final.mro())
#[Final, Minix1, Minix2, Header, object]
header = Final().get_header()
#run Minix1.get_header
#run Minix2.get_header
#run Headers.get_header
print(header)
#['data2', 'data1']
看來,運行得很成功,我們實現了多繼承協同工作的目標,通過混合不同個類,來模塊化地快速得到想要的header
屬性。
而這種工作方法,通過單純的重寫某個方法根本無法實現的,因為重寫任何方法,它會在MRO列表中找到最優先(也就是最靠前)的擁有同名方法的類,然后調用該方法,並且終止檢索,某項屬性僅僅會被一個方法所影響。
這個特性,在Django的CBV中有相當程度的應用。
相關文章或參考:
Python進階-繼承中的MRO與super
python 繼承與多重繼承