本文写作目的:试图以最清晰简洁的方式介绍python新式类的C3算法
python经典类和新式类:
python中的新式类是指直接或间接继承自object的类,而经典类则是没有直接或间接继承object的类,新式类和经典类的重要区别就是它们的MRO(方法解析顺序)不同,经典类的mro顺序是用深度优先+从左至右的方法确定的(这里的"从左至右"是指定义子类时,多个基类的书写顺序从左至右),而新式类的MRO顺序则是用C3算法确定的(2.3版本不是C3,之后都是C3)。在python3中只支持新式类,即所有类都直接或者简介继承自object类,并且,定义一个类时如果没有显示写出基类,则默认继承自object类;在python2(2.3及以上)中则有经典类和新式类两种方式,在python2中定义新式类必须显示写出继承object类。
新式类的C3算法:
方法解析顺序MRO其实就是确定子类和祖先类之间的一个线性顺序,这个顺序表达了各个祖先类(包括父类)对于子类的重要性(优先级),当类对象调用某个方法时,解释器会根据MRO列表去挨个找,直到找到一个同名的方法然后调用之,所以理解python继承结构中各个类的优先顺序对于写代码是必须的,C3算法过程如下。
假设子类C的N个直接父类是B1,B2,…,BN,记L[C]是C的线性化列表(MRO表),L[B1],L[B2],…,L[BN]是N个父类的线性化列表,F=[B1,…,BN]是由N个父类构成的单个列表,其中B1,…,BN是按照定义类C时的书写顺序从左至右的。则L[C]就是由N+1个列表生成的,具体就是用下面的递归合并算法:
L[C] = [C] + merge(L[B1], L[B2] ,…, L[BN], F)
L[object] = object
其中的merge的输入是N+1个列表,按照如下方式输出一个长度为N的列表:
- 检查第一个列表的头元素(如 L[B1] 的头),记作 H。
- 若 H 未出现在其它列表的中,或者出现了但也都是在头部,则将其输出,并将其从所有列表中删除,然后回到步骤1,否则,取出下一个列表的头部记作 H,继续该步骤。重复上述步骤,直至列表为空或者不能再找出可以输出的元素。如果是前一种情况,则算法结束;如果是后一种情况,说明无法构建继承关系,Python 会抛出异常。
可能第一次看这个过程不是很能理解,举个栗子就很清晰啦。(图是偷来的,嘻嘻),需要注意一下,图中多个父类的左右顺序和继承顺序是一致的。
如上图的继承结构,现在要求类A的MRO列表,记O为object,假设已经求出各个祖先类的MRO表如下:
L[D] = [D,O]
L[E] = [E,O]
L[F] = [F,O]
L[B] = [B,D,E,O]
L[C] = [C,D,F,O]
则L[A]的合并过程如下:
L[A] = A + merge([B,D,E,O],[C,D,F,O],[B,C]) # 取出B </