引子:

如图反映了python3中,几个类的继承关系和查找顺序。对于类A,其查找顺序为:A,B,E,C,F,D,G,(Object),这并不是一个简单的深度优先或广度优先的规律。那么这个顺序到底是如何产生的?

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。但是这样就造成重复。为防止重复,还得定义其他规范。

最后,利用python实现mro的生成。代码可用,但是用了递推函数,有机会以生成器的方式优化防止栈溢出。

 def not_in_tail(t, L):
# 判断一个元素是不是在一个列表的尾巴中出现过。如果从未出现,返回真。
if not L:
return True
if len(L) == 1:
return True
if t in L[1:]:
return False
else:
return True def mro(cls):
# 如果一个类没有任何父类,那么它的线性表里只有它自己。其实这个类就是object
if not cls.__bases__:
return [cls, ]
# 如果一个类只有一个父类object,那么它的线性表里是先找它自己,再找object
if cls.__bases__ == (object,):
return [cls, object]
# output用于产出线性表,第一项肯定是该类自己。
output = [cls, ]
# 这里使用递归方法,拿到它所有父类的线性表。后一项为所有父类的列表。
merge = [mro(parent) for parent in cls.__bases__] + [list(cls.__bases__), ]
while True:
# merge操作过程中会不断地把元素取出,可能会有子列表被取空,这时候应直接删除
while [] in merge:
merge.remove([])
# merge操作的终极目标,就是全部剩下object,这就是while的终止条件
if all([t == [object, ] for t in merge]):
merge = [object, ]
break
# 准备将欲取出的元素放在head中。该行是一个变量初始化。
head = None
# 遍历所有的子列表,同时还要拿到索引。
for index, sublist in enumerate(merge):
# 如果当前子列表只有object,那么就跳过
if sublist == [object, ]:
continue
# 判断子列表的第一项是否满足条件:从未在任何列表的尾巴中出现。如果满足此条件,记下此元素,退出循环准备删除
if all([not_in_tail(sublist[0], l) for l in merge[index:]]):
head = sublist[0]
break
if head:
# 将该元素添加到线性表中
output.append(head)
# 将该元素从所有子列表中删除
for l in merge:
if head in l:
l.remove(head)
# 从最终返回的列表可以看出产生线性表的两部分结构。merge的终极目标就是只剩下[object,],补上即可
mro_list = output + [object, ]
return mro_list # 以下是测试用例
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 print(mro(Z)) print(mro(O))

输出结果为:

 [<class '__main__.Z'>, <class '__main__.K1'>, <class '__main__.K2'>, <class '__main__.K3'>, <class '__main__.D'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.E'>, <class '__main__.O'>, <class 'object'>]

 [<class '__main__.O'>, <class 'object'>]

可以通过__mro__方法验证:

 print(Z.__mro__)

 (<class '__main__.Z'>, <class '__main__.K1'>, <class '__main__.K2'>, <class '__main__.K3'>, <class '__main__.D'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.E'>, <class '__main__.O'>, <class 'object'>)

当然,__mro__方法返回的是元组。所以前面的python代码可以利用tuple()改成以元组形式返回。在递推时,加一层list()以元组形式传入。不再展开。

回到开头的引子。经过验证,答案完全正确:

class G:pass
class E(G):pass
class B(E):pass
class F(G):pass
class C(F):pass
class D(G):pass
class A(B,C,D):pass print(mro(A))
print(A.__mro__) [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.F'>, <class '__main__.D'>, <class '__main__.G'>, <class 'object'>] (<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.F'>, <class '__main__.D'>, <class '__main__.G'>, <class 'object'>)

用python实现MRO算法的更多相关文章

  1. 【转】你真的理解Python中MRO算法吗?

    你真的理解Python中MRO算法吗? MRO(Method Resolution Order):方法解析顺序. Python语言包含了很多优秀的特性,其中多重继承就是其中之一,但是多重继承会引发很多 ...

  2. 你真的理解Python中MRO算法吗?[转]

    [前言] MRO(Method Resolution Order):方法解析顺序.Python语言包含了很多优秀的特性,其中多重继承就是其中之一,但是多重继承会引发很多问题,比如二义性,Python中 ...

  3. Python之MRO及其C3算法

    [<class '__main__.B'>, <class '__main__.A'>, <class 'object'>] (<class '__main_ ...

  4. Python多继承之MRO算法

    MRO即Method Resolution Order   方法解析顺序,它的提出主要是为了解决Python中多继承时,当父类存在同名函数时,二义性的问题 下面先看一个例子: import inspe ...

  5. Python 多继承(新式类) 的mro算法

    转载自:http://www.cnblogs.com/panyinghua/p/3283831.html mro即method resolution order,主要用于在多继承时判断调的属性的路径( ...

  6. Python大神必须掌握的技能:多继承、super和MRO算法

    本文主要以Python3.x为例讲解Python多继承.super以及MRO算法. 1. Python中的继承 任何面向对象编程语言都会支持继承,Python也不例外.但Python语言却是少数几个支 ...

  7. Python进阶(十五)----面向对象之~继承(单继承,多继承MRO算法)

    Python进阶(十五)----面向对象之~继承 一丶面向对象的三大特性:封装,继承,多态 二丶什么是继承 # 什么是继承 # b 继承 a ,b是a的子类 派生类 , a是b的超类 基类 父类 # ...

  8. python多重继承C3算法

    python多重继承的MRO算法选择: 经典方式.Python2.2 新式算法.Python2.3 新式算法(C3).Python 3中只保留了最后一种,即C3算法 C3算法的解析: 1.多继承UML ...

  9. Python中MRO

    MRO(方法解析顺序) 当有多重继承时,基于“从左到右,深度优先原则”: class CommonBase(): def Method(self): print('CommonBase') class ...

随机推荐

  1. 还是畅通工程(hdu1233)并查集应用

    还是畅通工程 Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Sub ...

  2. java设计模式-----23、命令模式

    概念: Command模式也叫命令模式 ,是行为设计模式的一种.Command模式通过被称为Command的类封装了对目标对象的调用行为以及调用参数. 命令模式(Command Pattern)是一种 ...

  3. Java_万年历(简单)

    1.方法,需要一个年份,一个月份.然后在控制台输出日历 // 输入一个年份和一个月份显示日历 public static void printCalendar(int year, int month) ...

  4. Oracle Index 索引无效原因

    索引无效原因 最近遇到一个SQL语句的性能问题,修改功能之前的运行时间平均为0.3s,可是添加新功能后,时间达到了4~5s.虽然几张表的数据量都比较大(都在百万级以上),但是也都有正确创建索引,不知道 ...

  5. 使用cgroup进行系统资源使用限制

    环境:Centos 7 64 一.对某个进程限制它使用cpu为50% 1.先写一个占用cpu较高的脚本 x=0 while [ True ];do x=$x+1 done; 2.可以看到运行后cpu使 ...

  6. PHP 基础总结

    PHP(Hypertext Preprocessor)是一种被广泛应用的开源通用脚本语言,尤其适用于Web开发.可用于服务端脚本.命令行脚本.桌面应用程序三大领域. PHP 的 SAPI(服务器应用程 ...

  7. 【代码笔记】iOS-NSFileManager

    一,代码. #import "ViewController.h" @interface ViewController () @end @implementation ViewCon ...

  8. git 常见命令,规范 整理

    move commit to stage area(把本地的1个commit还原到 暂存区) git reset --soft HEAD~1 把其他的commit的合并到现在到分支:git cherr ...

  9. php 去除所有空格 包括中文空格圆角空格

    有的中文的半角,圆角空格或者段落符显示为空白的.可以用正则来处理 preg_replace("/(\s|\&nbsp\;| |\xc2\xa0)/","" ...

  10. OSGI企业应用开发(八)整合Spring和Mybatis框架(一)

    到目前为止,我们已经学习了如何使用Blueprint將Spring框架整合到OSGI应用中,并学习了Blueprint&Gemini Blueprint的一些使用细节.本篇文章开始,我们將My ...