C3线性优化算法

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 按照从左到右的顺序深度遍历类的继承图,从而确定类中函数的调用顺序。这个过程具体如下:

  1. 检查当前的类里面是否有该函数,如果有则直接调用。
  2. 检查当前类的第一个父类里面是否有该函数,如果没有则检查父类的第一个父类是否有该函数,以此递归深度遍历。
  3. 如果没有则回溯一层,检查下一个父类里面是否有该函数并按照 2 中的方式递归。

我们继续用第一个例子中的函数继承顺序来说明这个过程:
C3线性优化算法_第1张图片
按照上述深度递归的方式,函数 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。

还是不清楚,没关系,以下示例:
C3线性优化算法_第2张图片
以此为例,类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,该操作可以分为以下几个步骤:

  1. 选取 merge中的第一个列表记为当前列表 K。
  2. 令 h=head(K),如果 h没有出现在其他任何列表的 tail当中,那么将其加入到类 C的线性化列表中,并将其从merge中所有列表中移除,之后重复步骤 2。
  3. 否则,设置 K为merge中的下一个列表,并重复 2 中的操作。
  4. 如果 merge中所有的类都被移除,则输出类创建成功;如果不能找到下一个h,则输出拒绝创建类 C并抛出异常。

上面的过程看起来好像很复杂,我们用上图的例子来说明一下:
首先我们有L[Y]=Y,L[X]=X,然后立即可以得到:

L[A]=A+merge[L[Y],L[X],Y,X]=[A,Y,X]
L[B]=A+merge[L[Y],L[X],Y,X]=[B,Y,X]
根据公式:
L[F]=F+merge[L[A],L[B],A,B]=F+merge[[A,Y,X],[B,Y,X],]A,B]
我们首先选取 h=head(L[A])=A,发现A没有出现在其他任何列表的tail中,也就是尾部(第2个元素至最后一个),所以可以加入到类F的解析列表中(同时将 A 从其他列表中删去),所以得到:
L[F]=[F,A]+merge[[Y,X],[B,Y,X],B]
之后选取 h=head(L[A])=Y,发现 Y不能满足要求(因为Y在 L[B]的tail中出现), 所以根据步骤 3 选取下一个列表并令 h=head(L[B])=B,发现B没有出现在其他任何列表的tail中,也就是尾部(第2个元素至最后一个),所以可以加入到类F的解析列表中(同时将 B 从其他列表中删去),所以得到:
L[F]=[F,A,B]+merge[[Y,X],[Y,X]]
所以得到:
L[F]=[F,A,B,Y,X]
所以得出上图的查找顺序为F,A,B,Y,X

5、案例
C3线性优化算法_第3张图片
有了上面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)

输出:
C3线性优化算法_第4张图片

你可能感兴趣的:(python)