1、MRO(方法解析顺序)
MRO 全称方法解析顺序(Method Resolution Order)。它定义了 Python 中多继承存在的情况下,解释器查找函数解析的具体顺序。
什么是函数解析顺序?我们首先用一个简单的例子来说明。请看下面代码:
class A():
def who_am_i(self):
print("I am A")
class B(A):
pass
class C(A):
def who_am_i(self):
print("I am C")
class D(B,C):
pass
d = D()
d.who_am_i()
如果问在Python中使用 D 的实例对象调用who_am_i(),究竟执行的是A中的 who_am_i() 还是 C 中的who_am_i()?
回答:是C中的who_am_i()。没错,但是历史并没有这么简单。
在介绍不同版本的 MRO 算法之前,我们有必要简单地回顾一下 Python 中类定义方式的发展历史。
版本 | MRO |
---|---|
python 2.1 | 经典类 DFS算法(即深度优先搜索) |
python 2.2 | 引入新式类 DFS和BFS算法(即广度优先搜索) |
python 2.3–2.7 | 经典类和新式类并存 DFS和C3算法 |
python 2.3–2.7 | 经典类和新式类并存 DFS和C3算法 |
python 3 | 新式类 C3算法 |
2、理解经典类的MRO
Python 2 按照从左到右的顺序深度遍历类的继承图,从而确定类中函数的调用顺序。这个过程具体如下:
我们继续用第一个例子中的函数继承顺序来说明这个过程:
按照上述深度递归的方式,函数 d.who_am_i() 调用的搜索顺序是 D, B, A, C, A。由于一个类不能两次出现,因此在搜索路径中去除掉重复出现的 A,得到最终的方法解析顺序是 D, B, A, C。
产生问题:
子类C又重写了who_am_i()方法,那么按照DFS顺序必定是会先找到B的属性或方法,B没有的话就会去上一层A寻找。那么C的who_am_i()方法将永远访问不到。
3、新式类的MRO
为了使类和内置类型更加统一,引入了新式类。新式类的每个类都继承于一个基类,可以是自定义类或者其它类,默认承于object。子类可以调用父类的构造函数。
新算法(BFS)与基于深度遍历的算法(DFS)类似,但是不同在于新算法会对深度优先遍历得到的搜索路径进行额外的检查。其从左到右扫描得到的搜索路径,对于每一个节点解释器都会判断该节点是不是好的节点。如果不是好的节点,那么将其从当前的搜索路径中移除。
那么问题在于,什么是一个好的节点?我们说 N 是一个好的节点当且仅当搜索路径中 N 之后的节点都不继承自 N。
我们还以上面的类继承图为例,按照深度优先遍历得到类 D 中函数的搜索路径 D, B, A, C, A。之后 Python 解释器从左向右检查时发现第三个节点 A 不是一个好的节点,因为 A 之后的节点 C 继承自 A。因此其将 A 从搜索路径中移除,然后得到最后的调用顺序 D, B, C, A。
还是不清楚,没关系,以下示例:
以此为例,类F继承至类A和类B,类A又继承至类Y和类X,类B又继承至类Y和类X。
所以查找顺序是F,A,Y,X,B,Y,X
因为A出现之后,后面的搜索路径没有出现A,所以可以说A是一个好节点。
同理,在Y和X出现以后,类B又继承了Y和X,所以Y和X不是一个好节点,将其从搜索路径中移除后为F,A,B,Y,X。
4、C3算法
C3算法解决了单调性问题和只能继承无法重写问题。
在介绍算法之前,我们首先约定需要使用的符号。我们用
head(C1C2⋯Cn)=C1 表示列表中的第一个元素
tail(C1C2⋯Cn)=C2C3⋯Cn 表示列表中的第二个至最后一个元素
为了方便做列表连接操作,我们记:
C1+(C2C3....Cn)
假设类 C继承自父类 B1,⋯Bn,那么根据 C3 线性化,类C的方法解析列表通过如下公式确定:
L[C(B1⋯Bn)]=C+merge(L[B1],⋯,L[Bn],B1⋯Bn)
这个公式表明C的解析列表是通过对其所有父类的解析列表及其父类一起做merge操作所得到。
接下来我们介绍 C3 线性化中最重要的操作 merge,该操作可以分为以下几个步骤:
上面的过程看起来好像很复杂,我们用上图的例子来说明一下:
首先我们有L[Y]=Y,L[X]=X,然后立即可以得到:
5、案例
有了上面C3算法的示例,我们来找出类Z的查找顺序:
首先可以得出
L[O]=O
L©=C+merge[L[O],[O]]=C,O
L(A)=A+merge[L[O],[O]]=A,O
L(B)=A+merge[L[O],[O]]=B,O
L(D)=A+merge[L[O],[O]]=D,O
L(E)=A+merge[L[O],[O]]=E,O
L(K1)=K1+merge(L[C],L[A],L[B],[C,A,B])
=K1+merge([C,O],[A,O],[B,O],[C,A,B])
=[K1,C]+merge([O],[A,O],[B,O],[A,B])
=[K1,C, A]+merge([O],[O],[B,O],[B])
=[K1,C, A,B]+merge([O],[O],[O])
=[K1,C, A,B,O]
同理
L(K3)=[K3,A,D,O]
L(K2)=[K2,B,D,E,O]
L(Z)=Z+merge(L[K1],L[K2],L[K3],[K1,K2,K3])
=Z+merge(L[K1,C, A,B,O],L[K3,A,D,O],L[K2,B,D,E,O],[K1,K2,K3])
=[Z,K1]+merge(L[C, A,B,O],L[K3,A,D,O],L[K2,B,D,E,O],[K2,K3])
=[Z,K1,C]+merge(L[ A,B,O],L[K3,A,D,O],L[K2,B,D,E,O],[K2,K3])
=[Z,K1,C,K3]+merge(L[ A,B,O],L[A,D,O],L[K2,B,D,E,O],[K2])
=[Z,K1,C,K3,A]+merge(L[ B,O],L[D,O],L[K2,B,D,E,O],[K2])
=[Z,K1,C,K3,A,K2]+merge(L[ B,O],L[D,O],L[B,D,E,O])
=[Z,K1,C,K3,A,K2,B]+merge(L[O],L[D,O],L[D,E,O])
=[Z,K1,C,K3,A,K2,B,D]+merge(L[O],L[O],L[E,O])
=[Z,K1,C,K3,A,K2,B,D,E]+merge(L[O],L[O],L[O])
=[Z,K1,C,K3,A,K2,B,D,E,O]
所以类Z的查找顺序为:Z,K1,C,K3,A,K2,B,D,E,O
验证:
class A(object):
pass
class B(object):
pass
class C(object):
pass
class D(object):
pass
class E(object):
pass
class K1(C, A, B):
pass
class K2(B, D, E):
pass
class K3(A, D):
pass
class Z(K1, K3, K2):
pass
for z in Z.__mro__:
print(z)