前言
super 是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承,会涉及到查找顺序(MRO)、重复调用(钻石继承)等种种问题。
一、super用法
我们先简单的理解为super().xx相当于调用了父类中的xx方法(实际上在单继承中是这样,多继承中有点区别)。
时候会看到像下面这样直接调用父类的一个方法:
class Base:
def __init__(self):
print('Base.__init__')
class A(Base):
def __init__(self):
Base.__init__(self)
print('A.__init__')
尽管对于大部分代码而言这么做没什么问题,但是在更复杂的涉及到多继承的代码中就有可能导致很奇怪的问题发生。 比如,考虑如下的情况:
class Base:
def __init__(self):
print('Base.__init__')
class A(Base):
def __init__(self):
Base.__init__(self)
print('A.__init__')
class B(Base):
def __init__(self):
Base.__init__(self)
print('B.__init__')
class C(A,B):
def __init__(self):
A.__init__(self)
B.__init__(self)
print('C.__init__')
c = C()
如果你运行这段代码就会发现 Base.__init__()
被调用两次,如下所示:
Base.__init__
A.__init__
Base.__init__
B.__init__
C.__init__
但是当我们在代码中换成使用 super()
,结果就很完美了:
class Base:
def __init__(self):
print('Base.__init__')
class A(Base):
def __init__(self):
super().__init__()
print('A.__init__')
class B(Base):
def __init__(self):
super().__init__()
print('B.__init__')
class C(A,B):
def __init__(self):
super().__init__() # Only one call to super() here
print('C.__init__')
运行这个新版本后,你会发现每个 __init__()
方法只会被调用一次了:
Base.__init__
B.__init__
A.__init__
C.__init__
二、super的本质
先说说python中如何实现继承---------对于你定义的每一个类,Python会计算出一个所谓的方法解析顺序(MRO)列表。 这个MRO列表就是一个简单的所有基类的线性顺序表。为了实现继承,Python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
而这个MRO列表的构造是通过一个C3线性化算法来实现的。 我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
- 子类会先于父类被检查
- 多个父类会根据它们在列表中的顺序被检查
- 如果对下一个类存在两个合法的选择,选择第一个父类
虽然名义上来说super是用来调用父类中的方法,但是super实际上是在MRO表中找到下一个匹配的类。super原型如下:
def super(cls, inst):
mro = inst.__class__.mro()
return mro[mro.index(cls) + 1]
两个参数 cls 和 inst 分别做了两件事:
1. inst 负责生成 MRO 的 list
2. 通过 cls 定位当前 MRO 中的 index, 并返回 mro[index + 1]
我们来看一个例子,猜猜下面的输出会是什么呢:
class A():
def __init__(self):
print("Enter A")
class B(A):
def __init__(self):
print("Enter B")
super(B,self).__init__()
print("Leave B")
class C(A):
def __init__(self):
print("Enter C")
super(C,self).__init__()
print("Leave C")
class D(B,C):
def __init__(self):
print("Enter D")
super(D,self).__init__()
print("Leave D")
d = D()
直接看结果:
很多人将super简单的理解为调用父类中的方法,可能认为应该是D调用B和C,由于B在左边,按顺序先调用B,B油调用A,完成之后轮到D调用C,C调用A.输出变成下面这样:
Enter D
Enter B
Enter A
Leave B
Enter C
Enter A
Leave C
Leave D
但是根据我们上面说的super本质知道 super 和父类其实没有实质关联,我们就不难理解为什么 enter B 下一句是 enter C 而不是 enter A了(如果认为 super 代表“调用父类的方法”,会想当然的认为下一句应该是enter A)。流程如下,在 B 的 __init__ 函数中:
super(B,self).__init__() 首先获取self.__class__.__mro__,但是这里的self是D的实例,而不是B的。
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
然后,通过 B 来定位 MRO 中的 index,并找到下一个。显然 B 的下一个是 C。于是,我们调用 C 的 __init__,打出 enter C。
当你使用 super()
函数时,Python会在MRO列表上继续搜索下一个类。 只要每个重定义的方法统一使用 super()
并只调用它一次, 那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次。