MRO
对于支持继承的编程语言来说,其方法(属性)可能定义在当前类,也可能来自于基类,所以在方法调用时就需要对当前类和基类进行搜索以确定方法
所在的位置。而搜索的顺序就是所谓的「方法解析顺序」(Method Resolution Order,或MRO)。对于只支持单继承的语言来说,MRO 一般比较简单;
而对于 Python 这种支持多继承的语言来说,MRO 就复杂很多。
先看一个「菱形继承」的例子:
如果 x 是 D 的一个实例,那么 x.show() 到底会调用哪个 show 方法呢?
1)如果按照 [D, B, A, C] 的搜索顺序,那么 x.show() 会调用 A.show();
2)如果按照 [D, B, C, A] 的搜索顺序,那么 x.show() 会调用 C.show()。
由此可见,MRO 是把类的继承关系线性化的一个过程,而线性化方式决定了程序运行过程中具体会调用哪个方法。
既然如此,那什么样的 MRO 才是最合理的?Python 中又是如何实现的呢?
Python 至少有三种不同的 MRO:
1)经典类(classic class)的深度遍历。
2)Python 2.2 的新式类(new-style class)预计算。
3)Python 2.3 的新式类的 C3 算法。它也是 Python 3 唯一支持的方式。
1. 经典类中的 MRO
Python 有两种类:经典类(classic class)和新式类(new-style class)。两者的不同之处在于新式类继承自 object。
1)在 Python 2.1 以前,经典类是唯一可用的形式;
2)Python 2.2 引入了新式类,使得类和内置类型更加统一;
3)在 Python 3 中,新式类是唯一支持的类。
经典类采用了一种很简单的 MRO 方法:从左至右的深度优先遍历。以上述「菱形继承」为例,其查找顺序为 [D, B, A, C, A],如果只保留重复
类的第一个则结果为 [D,B,A,C]。我们可以用 inspect.getmro(class) 来获取类的 MRO。
class A: def show(self): print("A.show()") class B(A): pass class C(A): def show(self): print("C.show()") class D(B, C): pass x = D() x.show()
这种深度优先遍历对于简单的情况还能处理的不错,但是对于上述「菱形继承」其结果却不尽如人意:虽然 C.show() 是 A.show() 的更具体化版
本(显示了更多的信息),但我们的x.show() 没有调用它,而是调用了 A.show()。这显然不是我们希望的结果。对于新式类而言,所有的类都继
承自 object,所以「菱形继承」是非常普遍的现象,因此不可能采用这种 MRO 方式。
2. Python 2.2中的新式类 MRO
为解决经典类 MRO 所存在的问题,Python 2.2 针对新式类提出了一种新的 MRO 计算方式:在定义类时就计算出该类的 MRO 并将其作为类的属性。
因此新式类可以直接通过 __mro__ 属性获取类的 MRO。
对于新式类的 MRO,将是自左向右的广度遍历,上述钻石继承的顺序就变成了 [D,B,C,A,object],解决了菱形继承(下面左图)在经典类中存在的问题。
但是对于正常的继承关系(如下面右图),根据新式类中的广度遍历原则,查找顺序为[E,C,D,A,B,object],A 是 C 的唯一基类,但却在 C 之后先查询 D,
根据单调性,应该先从唯一基类进行查找。
注意:在 Python 2.2 到 Python3 之间的版本仍然存在经典类,在定义类时:
1)继承 object 才是新式类,MRO 按从左向右的深度优先原则;
2)没有继承 object 就是经典类,MRO 按从左向右的广度优先原则。
对比一下,左侧为 Python 2 中的经典类运行结果,右侧为 Python 2 中的新式类运行结果:
3. Python 2.3及其以后的新式类 MRO
新式类的 MRO 使用 C3 算法,并且在Python 3中只存在新式类。Python 2.3及其以后的新式类 MRO,使用的是拓扑排序,在一个有向无环图中:
1)从左到右选择一个入度为0的顶点并输出(入度:以某顶点为弧头,终止于该顶点的弧的数目)
2)从网中删除此顶点以及所有出边
3)重复步骤1、2,直到所有点都被遍历
举个几个例子:
----------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------