Python 的菱形继承和 MRO
Python 是一门(在我看来很不幸地)支持多继承的语言。既然支持了多继承,那难以避免的一个问题就是菱形继承。考虑下图的类关系:
对应代码:
class Base:
pass
class Father(Base):
def __init__(self):
super().__init__()
class Mother(Base):
def __init__(self):
super().__init__()
class Child(Father, Mother):
def __init__(self):
super().__init__()
当我们实例化一个 Child()
,它会怎么调用其父类的初始化函数呢?或者说,super().__init__()
这个调用过程中,实际发生了什么?
1. super()
不是父类
首先需要明确的是,调用 super()
得到的 并不是父类对象 。
如果 super()
只是简单地将调用委托给其全部的父类,那么我们就会得到这样的调用:
Child -> Father -> Base --> Mother -> Base
注意,这里不可避免地产生了两次 Base
调用,这种处理方法显然是存在问题的。
2. super()
和 MRO
python 的文档很清楚地指出, super()
得到的是 一个代理对象,将调用委托给type的父类或兄弟类。
在我们上面的例子中,Child
的 super()
得到的是一个 Father
对象的代理,因此在 Child.__init__
上下文中调用 super().__init__()
实际相当于 Father.__init__(self)
。
同样, Father
中的 super()
得到的是 Mother
对象的代理(而不是 Base
),Mother
中的 super()
才是 Base
对象的代理。
调用链条为
Child -> Father -> Mother -> Base (-> object)
这个顺序被称为 python 的 Method Resolution Order (MRO)。MRO 保证了在合法的继承中,每个类只会被遍历一次。因此,super()
委托的类就是 MRO 调用链中的下一个类。
在 python 中,可以使用 Class.mro()
获取类的 MRO 链。
3. MRO 的要求和实现
知道了 super()
的实现,我们进一步想知道 MRO 的要求和其在 python 中的实现。
考虑一个继承关系的集合,其应该构成一个有向无环图(我不应该继承我自己)。因此,一个合法的 MRO 应该是一个有向无环图的遍历。
对于继承关系,我们还需要一些要求:
MRO 应该是接近 BFS 的,因此可以从更接近最终类的地方查找实现。如果采用 DFS,可能查找到更 abstract 的实现。
MRO 应该是 单调的。这里,单调定义为:
- 如果在类 C 的线性化过程中,C1 在 C2 类之前,那么在类 C 的所有子类中,C1 都应该在 C2 类之前。
考虑这样的一个复杂继承关系:
假设在类 A 的线性化中,X 出现在 Y 之前(class A(X, Y)
);而在类 B 的线性化中,Y 出现在 X 之前(class B(Y, X)
)。这样一来,如果 C 继承于 A、B,那么类 C 就无法找到一个合法的 MRO 满足单调性。
稍微修改一下类 B 为 class B(X, Y)
,就可以算出一个满足要求的 MRO:
C -> A -> B -> X -> Y -> O
python 使用 C3 算法 来实现 MRO。其详细算法可以参见参考资料[1]。博文由于篇幅不再继续展开。