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 继承与多重继承

posted @ 2019-05-25 17:55  喵帕喵帕喵  阅读(18797)  评论(0编辑  收藏  举报