继承中的MRO与super详解
Python进阶-继承中的MRO与super
写在前面
如非特别说明,下文均基于Python3摘要
本文讲述python继承关系中如何通过super()调用“父类”方法,super(type, currentclass)返回currentclass的mro中type的下一个类的代理;以及如何设计python类以便正确初始化。
1. 单继承中父类方法调用
在继承中,调用父类方法是很有必要的。调用父类方法的场景有很多:
比如必须调用父类的构造方法__init__才能正确初始化父类实例属性,使得子类实例对象能够继承到父类实例对象的实例属性;
再如需要重写父类方法时,有时候没有必要完全摒弃父类实现,只是在父类实现前后加一些实现,最终还是要调用父类方法
单继承是最简单的继承关系,多继承过于复杂,而且使用起来容易出错。因此一些高级语言完全摒弃了多继承,只支持单继承;一些高级语言虽然支持多继承,但也不推荐使用多继承。Python也是一样,在不能完全掌握多继承时,最好不好使用,单继承能满足绝大部分的需求。
1.1 非绑定方式调用
绑定方法与非绑定方法的区别与联系参见:Python基础-类
如有以下继承关系两个类:
class D(object):def test(self):print('test in D')class C(D):def test(self):print('test in C') D.test(self)
现在要求在子类C的test函数中调用父类D的test实现。我们能想到最直接的方法恐怕是直接引用类对象D的函数成员test了:
class D(object):def test(self):print('test in D')class C(D):def test(self):print('test in C')
尝试测试一下:
c = C()c.test()
output:
test in Ctest in D
看来非绑定的方式确实满足了当前调用父类方法的需求。
1.2 builtin 函数 super
参考Python tutorial关于super的描述: super([type[, object-or-type]])
Return a proxy object that delegates method calls to a parent or sibling class of type. This is useful for accessing inherited methods that have been overridden in a class. The search order is same as that used by getattr() except that the type itself is skipped.
super函数返回委托类type的父类或者兄弟类方法调用的代理对象。super用来调用已经在子类中重写了的父类方法。方法的搜索顺序与getattr()函数相同,只是参数类type本身被忽略。
1.3 绑定方式调用
使用绑定方式调用父类方法,自然不能显式传入参数当前对象(self)。现在super函数能够范围对父类的代理,因为在单继承中子类有且仅有一个父类,所以父类是明确的,我们完全清楚调用的父类方法是哪个:
class D(object):def test(self):print('test in D')class C(D):def test(self):print('test in C')super().test() # super(C, self).test()的省略形式
2. 深入super
事实上,super函数返回的代理对象是一个bultin class super,正如它的名字所指,类super代理了子类的父类。在单继承关系中,super代理的类很容易找到吗,就是子类的唯一父类;但是在多继承关系中,super除了能代理子类的父类外,还有可能代理子类的兄弟类。
2.1 复杂的多继承
在多继承关系中,继承关系可能会相当复杂。
class D(object): def test(self):print('test in D')class C(D): def test(self):print('test in C')class B(D): def test(self):print('test in B')class A(B, C):pass
类A继承层次结构如下:
object | D / B C / A
类A的继承关系中存在菱形结构,即可以通过多条路径从类A到达某个父类,这里是D。
如果现在要求在类A中调用“父类”的test方法,需要一种对test方法的搜索解析顺序,来决定到底是调用B,C或D的test方法。
2.2 方法解析顺序(MRO)
上面提出的对test的方法的搜索顺序,就是方法解析顺序了。
深度优先
Python旧式类中,方法解析顺序是深度优先,多个父类从左到右。
广度优先
Python新式类中,方法解析顺序是广度优先,多个父类从左到右。
所以上面的解析顺序是:A -> B -> C -> D -> object。
Python中,类的__mro__属性展示了方法搜索顺序,可以调用mro()方法或者直接引用__mro__得到搜索顺序:
print(A.mro())print(A.__mro__)
output:
[<class>, <class>, <class>, <class>, <class>](<class>, <class>, <class>, <class>, <class>)</class></class></class></class></class></class></class></class></class></class>
所以
a = A()a.test() # output: test in B
变化的MRO
即使是同一个类,在不同的MRO中位置的前后关系都是不同的。如以下类:
class D(object): def test(self):print('test in D')class C(D): def test(self):print('test in C')class B(D): def test(self):print('test in B')
类B的继承层次结构为:
object | D / C B
类B的MRO:B -> D -> object
对比类A的MRO:A -> B -> C -> D -> object
同样的类B,在两个不同的MRO中位置关系也是不同的。可以说,在已有的继承关系中加入新的子类,会在MRO中引入新的类,并且改变解析顺序。
那么可以想象,同样在类B的test中通过super调用父类方法,在不同的MRO中实际调用的方法是不同的。
如下:
class D(object): def test(self):print('test in D')class C(D): def test(self):print('test in C')super().test()class B(D): def test(self):print('test in B')super().test()class A(B, C):passb = B()b.test()print('==========')a = A()a.test()
output:
test in Btest in D==========test in Btest in Ctest in D
因为在原有的类关系中加入B和C的子类A,使得在B的test方法中调用super的test方法发生了改变,原来调用的是其父类D的test方法,现在调用的是其兄弟类C的test方法。
从这里可以看出super不总是代理子类的父类,还有可能代理其兄弟类。
因此在设计多继承关系的类体系时,要特别注意这一点。
2.3 再看super方法
方法super([type[, object-or-type]]),返回的是对type的父类或兄弟类的代理。
如果第二个参数省略,返回的super对象是未绑定到确定的MRO上的:
如果第二个参数是对象,那么isinstance(obj, type)必须为True;
如果第二个参数是类型,那么issubclass(type2, type)必须为True,即第二个参数类型是第一个参数类型的子类。
在super函数的第二个参数存在时,其实现大概如以下:
def super(cls, inst): mro = inst.__class__.mro() # Always the most derived classreturn mro[mro.index(cls) + 1]
很明显,super返回在第二个参数对应类的MRO列表中,第一个参数type的下一个类的代理。因此,要求第一个参数type存在于第二个参数类的MRO是必要的,只有第一个参数类是第二个参数所对应类的父类,才能保证。
super()
super函数是要求有参数的,不存在无参的super函数。在类定义中以super()方式调用,是一种省略写法,由解释器填充必要参数。填充的第一个参数是当前类,第二个参数是self:
super() => super(current_class, self)
所以,super()这种写法注定只能在类定义中使用。
现在再来看上面的继承关系:
class D(object):def test(self):print('test in D')class C(D):def test(self):print('test in C')# super().test() # 与下面的写法等价super(C, self).test() # 返回self对应类的MRO中,类C的下一个类的代理class B(D):def test(self):print('test in B')# super().test() # 与下面的写法等价super(B, self).test() # 返回self对应类的MRO中,类B的下一个类的代理class A(B, C):pass
因此:
b = B()b.test() # 基于类B的MRO(B->D->object),类B中的super()代理Dprint('==========')a = A()a.test() # 基于类A的MRO(A->B->C->D->object),类B中的super()代理C