python中的super()用法以及多繼承協同任務


理解了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 繼承與多重繼承


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM