C3线性表和MRO

C3线性是用于获取多重继承下继承顺序的一种算法。通常,被称为方法解析顺序,即MRO(method resolution order)。

算法的名字“C3”并不是缩写,而是指该算法的三大重要属性:

1.前趋图。作为有向无环图,找不到任何的循环,通常用前趋图来理解程序的依赖关系。

2.保持局部的优先次序。

3.单调性。

C3是1996年首次被提出。在python2.3及后续版本中,C3被选定为默认的解析算法。

一个类的C3线性表,是由两部分进行merge操作得到的,第一部分是是它所有父类的C3线性表(parents' linearizations),第二部分是它所有父类组成的列表(parents list)。后者其实是局部的优先级列表。

所谓merge操作,遵循以下原则:表的首个元素不可以出现在其他地方,如果出现了这样的情形,那么就要将该元素全部移出,放到产出列表(output list)中。如果循环进行这一操作,就可以把所有的表逐步移出,逐步扩张产出表,最后得到一个纯粹的产出表。这个产出表就是最后的C3线性表。

举个例子:

python3代码:

class O:
    pass
class A(O):
    pass
class B(O):
    pass
class C(O):
    pass
class D(O):
    pass
class E(O):
    pass
class K1(A, B, C):
    pass
class K2(D, B, E):
    pass
class K3(D, A):
    pass
class Z(K1, K2, K3):
    pass

即:

O从以下类继承:无(实际上python3中默认为object类,因为所有类继承于object类,所以才有多种多样的内置方法可用)

A从以下类继承:O

B从以下类继承:O

C从以下类继承:O

D从以下类继承:O

E从以下类继承:O

K1从以下类继承:A,B,C

K2从以下类继承:D,B,E

K3从以下类继承:D,A

Z从以下类继承:K1,K2,K3

为方便起见,记类cls的线性表为L[cls]。

首先,从最简单的类O开始:

L[O]:平凡的情形,直接定为列表[O],即线性表的第一项是自身。所以,L[0]=[O]

L[A]:类A的所有父类是O,所以前一部分是L[O],后一部分是类A所有父类列表[O],前面已经得出L[O]=[O],因此L[A] = [A] + merge(L[O] + [O]) = [A]+merge([O] + [O]) = [A] + [O] = [A,O]

同理:

L[B]=[B,O]

L[C]=[C,O]

L[D]=[D,O]

L[E]=[E,O]

L[K1]:线性表第一项为自身K1,以后的项为其所有父类C3线性表和其所有父类列表的并——

K1继承于A,B,C,所以所有父类C3线性表为:L[A],L[B],L[C];所有父类列表为:A,B,C。

并起来就是merge(L[A],L[B],L[C],A,B,C),然后,遵循原则一步步将其拆开。

L[K1]=[K1]+merge(L[A],L[B],L[C],[A,B,C])

=[K1]+merge([A,O],[B,O],[C,O],[A,B,C])——元素A只在这些列表的首项出现(如:[A,O]和[A,B,C]),应当把它移除到产出列表(output list)。

=[K1,A]+merge([O],[B,O],[C,O],[B,C])——元素O在列表的首项出现过(如:[O]),也在有些列表的剩余项出现过(如[B,O],[C,O]),所以保留它。但是,元素B只在这些列表的首项出现(如:[B,O],[B,C]),应当移出它。

=[K1,A,B]+merge([O],[O],[C,O],[C])——移出B后,同理发现C也是要移出的

=[K1,A,B,C]+merge([O],[O],[O])——merge操作已经走到尽头了

=[K1,A,B,C,O]

L[K2]:K2继承于D,B,E,所以所有父类C3线性表为L[D],L[B],L[E],所有父类列表为D,B,E。同理可得:

L[K2]=[K2]+merge([D,O],[B,O],[C,O],[D,B,E])

=[K2,D]+merge([O],[B,O],[C,O],[B,E])

=[K2,D,B]+merge([O],[O],[C,O],[E])

=[K2,D,B,E]+merge([O],[O],[O],[O])

=[K2,D,B,E,O]

L[K3]:K3继承于D,A,所以所有父类的C3线性表为L[D],L[A],所有父类列表为D,A。同理可得:

L[K3]=[K3,D,A,O]

L[Z]:Z继承于K1,K2,K3。前面计算了K1,K2,K3的线性表,所以这里直接代入计算:

L[Z]=[Z]+merge(L[K1],L[K2],L[K3],K1,K2,K3)

=[Z]+merge([K1,A,B,C,O] , [K2,D,B,E,O] , [K3,D,A,O] , [K1,K2,K3])——应移出K1

=[Z,K1]+merge([A,B,C,O],[K2,D,B,E,O],[K3,D,A,O],[K2,K3])——应移出K2

=[Z,K1,K2]+merge([A,B,C,O],[D,B,E,O],[K3,D,A,O],[K3])——应移出K3

=[Z,K1,K2,K3]+merge([A,B,C,O],[D,B,E,O],[D,A,O])——应移出D

=[Z,K1,K2,K3,D]+merge([A,B,C,O],[B,E,O],[A,O])——应移出A

=[Z,K1,K2,K3,D,A]+merge([B,C,O],[B,E,O],[O])——应移出B

=[Z,K1,K2,K3,D,A,B]+merge([C,O],[E,O],[O])——应移出C

=[Z,K1,K2,K3,D,A,B,C]+merge([O],[E,O],[O])——应移出E

=[Z,K1,K2,K3,D,A,B,C,E]+merge([O],[O],[O])——耗尽,结束

=[Z,K1,K2,K3,D,A,B,C,E,O]

在python3中使用对类help()函数,可以很方便地查看MRO:

可以看出,python3中的MRO计算,不能以简单地找完一层再找上一层。假如以“广度优先、从左到右、绝不重复”这一规律概括,很容易误认为按照如下顺序查找:

Z从K1,K2,K3继承,所以前三项为K1,K2,K3。接下来找K1的父类A,B,C。再找K2的父类D,B,E,再找K3的父类D,A。但是这样就造成重复。为防止重复,还得定义其他规范。

猜你喜欢

转载自www.cnblogs.com/poincare/p/9075020.html