概念
Python MRO 定义了多继承类实例在调用类方法的时候的正确搜索路径。什么意思呢, 如果一个类有多个父类, 那么我们用这个类的实例调用类的方法的时候,Python是如何在当前类和父类中找到这个方法的呢?MRO 定义了一系列规则及算法来完成这个事情。
MRO的历史
MRO有两个版本, 这两个的分界线为Python 2.2, 在2.2(包括)之前Python只有一个MRO计算的算法,之后Python添加了一套新的算法(C3)来计算MRO, 这两个算法我们下面会详细解释。
使用范围
当我们看到【MRO的历史】部分的时候,我们会想是不是python 2.2 之后python都是用的是新的C3 算法, 之前都用的是老的算法,事实并不是这样的,为了保持Python 版本的向后兼容性,新的C3算法只会用在新式类(第一个父类继承了object类)。经典类(没有继承自object类)仍然使用来的MRO算法。举例来说:
class A:passclass B:passclass C(A, B):pass
C 类的实例将会使用老的MRO算法来计算方法调用的顺序,但是如果将上面的代码修改成:
class A(object):passclass B(object):pass
这时候,Python 将会使用新的C3 算法来计算。
老版本的MRO算法:
我们试一下下面的程序:
class A:def who_am_i(self):print "I am a A"class B(A):def who_am_i(self):print "I am a B"class C(A):def who_am_i(self):print "I am a C"class D(B, C):def who_am_i(self):print "I am a D"d1 = D()d1.who_am_i()
输出结果: I am a D
现在我们注释掉D类中的who_am_i 方法:
class D(B, C):# def who_am_i(self):# print "I am a D"pass
这个时候哪个方法会被调用呢?B 中的方法? C中的方法还是A中的方法?我们来看看输出结果:
输出结果: I am a B(这个结果大家估计可以猜出来)
那么现在我们注释掉B类中的方法:
class B(A):# def who_am_i(self):# print "I am a B"pass
现在会执行哪个方法呢?大家可能会觉得C类中的方法会被调用, 因为C是D类的另外一个直接父类。
但是当我们执行程序以后发现输出结果是:
输出结果: I am a A
在解释这个执行过程之前, 我们先把后续的测试做完。
现在我们将A类中的方法注释掉
class A:# def who_am_i(self):# print "I am a A"pass
输出结果: I am a C
然后我们取消A类中的注释, 然后显式的指定A继承自object类
class A(object):def who_am_i(self):print "I am a A"
输出结果:I am a C
这个时候我们发现如果A不显式继承自object时的输出结果为 I am a A, 如果继承自object 输出结果则为I am a C
大家可能会发现当A不继承自object的时候, python先会执行A中的方法,如果继承自object的话则会先执行C中的方法。 这个就是我们下要介绍的来的MRO 算法。
如果一个类继承自多个父类,此类的实例调用一个方法的时候,Python会生成一个类列表用来搜索哪个类中的方法会被调用。这个算法是一个树型路由,算法的方法查找顺序为:
沿着树深度搜索
从左到右去搜索
具体的执行顺序为:
先查找是否调用的方法在当前类实例的类中定义
如果不存在,则在第一个父类中去查找,然后去父类的父类中去找, 依次类推
如果第二步仍然没有找到调用的方法,则会查看当前类是否继承自另外一个父类,然后按照之前的顺序一次查找
按照这个顺序在我们的例子中方法的查找顺序为: D, B, A, C, A
Python 规定搜索路径中的类只能出现一次, 所以最终的搜索的路径为: D, B, A, C
新的MRO(C3) 算法
开始之前我们需要做一些简单的约定, 接下来我会使用:
类列表[C1, C2, …, CN]
用head 来代表类列表的第一个元素: head = C1
用tail 来代表类列表中的剩余部分:tail = [C2,…,CN]
如果一个列表中的第一个元素不存在于其他列表的tail部分, Python 认为这个head 就是一个good head.
接下来我们我们详细解释MRO是如何工作的:
假设类C继承自B1,B2,…,BN,我们打算完成类C的线性化L[C], 规则是:
C的线性化结果为C加上所有父类线性化及所有父类列表进行merge运算的结果, 公式为:
L[C(B1…BN)] = C + merge(L(B1), …, L(BN), B1, …, BN)
如果C是object对象, Python 约定object线性化结果为:
L[object] = object
merge 运算的规则为:
获取merge中第一个list的head值, 例如: L[B1][0]; 如果此head是一个good head, 则添加head到C的列表中,然后从merge的lists中删除head,否则的话查找下一个list中的head,重复以上操作直到所有类被删除或者找不到一个good head。如果不能merge Python 2.3 则会拒绝创建类对象并且会raise一个异常.
如果C类只有一个父类(单继承),merge 计算则非常简单, 例如:
L(C(B)) = C + merge(L(B), B) = C+ L[B]
下面我们来举例来完成多继承:
O = objectclass F(O):passclass E(O):passclass D(O):passclass C(D, F):passclass B(D, E):passclass A(B, C):pass
这个例子中类的继承图如下:
6---Level 3 | O | (more general)/ ---/ | |/ | |/ | |--- --- --- |Level 2 3 | D | 4| E | | F | 5 |--- --- --- |_ / | |/ _ | |/ | |--- --- |Level 1 1 | B | | C | 2 |--- --- |/ |/ /---Level 0 0 | A | (more specialized)---
类O、D、E、F的线性化非常简单:
L[O] = [object]L[D] = [D, O]L[E] = [E, O]L[F] = [F, O]
下面我们来计算类B的线性化:
L[B] = [B] + merge(L(D), L(E), [D, E])= [B] + merge([D, O], [E, O], [D,E])
按照merge 的规则, 我们可以看到D 是一个good head, 这个时我们可以得到如下结果:
L[B] = [B, D] + merge([O], [E, O], [E])
接下来我们获取O 发现, O并不是一个good head, 因为O 在[E, O] 这个list的tail 部分中,这个时候我们跳过O元素, 然后我们继续获取E 发现它是一个good head, 这个时候我们将得到:
L[B] = [B, D, E, O]
使用同样的过程我们可以得到C的线性化列表:
L[C] = [C] + merge([D, O],[F, O], [D,F])= [C, D] + merge([O], [F, O], [F])= [C, D, F] + merge([O], [O])= [C, D, F, O]
A类的线性化列表:
L[A] = [A] + merge([B, D, E, O], [C, D, F, O], [B, C])= [A, B] + merge([D, E, O], [C, D, F, O], C)= [A, B, C] + merge([D, E, O], [D, F, O])= [A, B, C, D] + merge([E, O], [F, O])= [A, B, C, D, E] + merge(O, [F, O])= [A, B, C, D, E, F] + merge([O], [O])= A B C D E F O
后记
我们了解了Python 的MRO 计算方法以后, 可以在实际的工作中发现如果每次都通过这样来计算的话非常的麻烦, 那么我们如何才能更快的获取到一个类的MRO 顺序呢, Python中提供了一个内置函数mro()可以帮我们快速的获取到这个结果, 例如: 我有一个A类, 通过调用A.mro() 我们会得到如下结果:
>>> A.mro()(, , ,, , ,)
参考资料: