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列表中找到最优先(也就是最靠前)的拥有同名方法的类,然后调用该方法,并且终止检索,某项属性仅仅会被一个方法所影响。
相关文章或参考:
Python进阶-继承中的MRO与super
python 继承与多重继承