你真的理解Python中MRO算法吗?

MRO(Method Resolution Order):方法解析顺序。

Python语言包含了很多优秀的特性,其中多重继承就是其中之一,但是多重继承会引发很多问题,比如二义性,Python中一切皆引用,这使得他不会像C++一样使用虚基类处理基类对象重复的问题,但是如果父类存在同名函数的时候还是会产生二义性,Python中处理这种问题的方法就是MRO。

【历史中的MRO】

如果不想了解历史,只想知道现在的MRO可以直接看最后的C3算法,不过C3所解决的问题都是历史遗留问题,了解问题,才能解决问题,建议先看历史中MRO的演化。

Python2.2以前的版本:金典类(classic class)时代

金典类是一种没有继承的类,实例类型都是type类型,如果经典类被作为父类,子类调用父类的构造函数时会出错。

这时MRO的方法为DFS(深度优先搜索(子节点顺序:从左到右))。

  1. Class A: # 是没有继承任何父类的
    def __init__(self):
    print "这是金典类"

inspect.getmro(A)可以查看金典类的MRO顺序

  1. import inspect
    class D:
    pass
  2.  
  3. class C(D):
    pass
  4.  
  5. class B(D):
    pass
  6.  
  7. class A(B, C):
    pass
  8.  
  9. if __name__ == '__main__':
    print inspect.getmro(A)
  10.  
  11. >> (<class __main__.A at 0x10e0e5530>, <class __main__.B at 0x10e0e54c8>, <class __main__.D at 0x10e0e53f8>, <class __main__.C at 0x10e0e5460>)

MRO的DFS顺序如下图:

两种继承模式在DFS下的优缺点。

第一种,我称为正常继承模式,两个互不相关的类的多继承,这种情况DFS顺序正常,不会引起任何问题;

第二种,棱形继承模式,存在公共父类(D)的多继承(有种D字一族的感觉),这种情况下DFS必定经过公共父类(D),这时候想想,如果这个公共父类(D)有一些初始化属性或者方法,但是子类(C)又重写了这些属性或者方法,那么按照DFS顺序必定是会先找到D的属性或方法,那么C的属性或者方法将永远访问不到,导致C只能继承无法重写(override)。这也就是为什么新式类不使用DFS的原因,因为他们都有一个公共的祖先object。

Python2.2版本:新式类(new-style class)诞生

为了使类和内置类型更加统一,引入了新式类。新式类的每个类都继承于一个基类,可以是自定义类或者其它类,默认承于object。子类可以调用父类的构造函数。

这时有两种MRO的方法

1. 如果是金典类MRO为DFS(深度优先搜索(子节点顺序:从左到右))。

2. 如果是新式类MRO为BFS(广度优先搜索(子节点顺序:从左到右))。

  1. Class A(object): # 继承于object
    def __init__(self):
    print "这是新式类"
  1. A.__mro__ 可以查看新式类的顺序

MRO的BFS顺序如下图:

两种继承模式在BFS下的优缺点。

第一种,正常继承模式,看起来正常,不过实际上感觉很别扭,比如B明明继承了D的某个属性(假设为foo),C中也实现了这个属性foo,那么BFS明明先访问B然后再去访问C,但是为什么foo这个属性会是C?这种应该先从B和B的父类开始找的顺序,我们称之为单调性。

第二种,棱形继承模式,这种模式下面,BFS的查找顺序虽然解了DFS顺序下面的棱形问题,但是它也是违背了查找的单调性。

因为违背了单调性,所以BFS方法只在Python2.2中出现了,在其后版本中用C3算法取代了BFS。

Python2.3到Python2.7:金典类、新式类和平发展

因为之前的BFS存在较大的问题,所以从Python2.3开始新式类的MRO取而代之的是C3算法,我们可以知道C3算法肯定解决了单调性问题,和只能继承无法重写的问题。C3算法具体实现稍后讲解。

MRO的C3算法顺序如下图:看起简直是DFS和BFS的合体有木有。但是仅仅是看起来像而已。

Python3到至今:新式类一统江湖

Python3开始就只存在新式类了,采用的MRO也依旧是C3算法。

【神奇的算法C3】

C3算法解决了单调性问题和只能继承无法重写问题,在很多技术文章包括 官网 中的C3算法,都只有那个merge list的公式法,想看的话网上很多,自己可以查。但是从公式很难理解到解决这个问题的本质。我经过一番思考后,我讲讲我所理解的C3算法的本质。如果错了,希望有人指出来。

假设继承关系如下( 官网 的例子):

  1. class D(object):
    pass
  2.  
  3. class E(object):
    pass
  4.  
  5. class F(object):
    pass
  6.  
  7. class C(D, F):
    pass
  8.  
  9. class B(E, D):
    pass
  10.  
  11. class A(B, C):
    pass
  12.  
  13. if __name__ == '__main__':
    print A.__mro__

首先假设继承关系是一张图(事实上也是),我们按类继承是的顺序(class A(B, C)括号里面的顺序B,C),子类指向父类,构一张图。

我们要解决两个问题:单调性问题和不能重写的问题。

很容易发现要解决单调性,只要保证从根(A)到叶(object),从左到右的访问顺序即可。

那么对于只能继承,不能重写的问题呢?先分析这个问题的本质原因,主要是因为先访问了子类的父类导致的。那么怎么解决只能先访问父类再访问子类的问题呢?如果熟悉图论的人应该能马上想到拓扑排序,这里引用一下百科的的定义:

对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边(u,v)∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。

因为拓扑排序肯定是根到叶(也不能说是叶了,因为已经不是树了),所以只要满足从左到右,得到的拓扑排序就是结果,关于拓扑排序算法,大学的数据结构有教,这里不做讲解,不懂的可以自行谷歌或者翻一下书,建议了解完算法再往下看。

那么模拟一下例子的拓扑排序:首先找入度为0的点,只有一个A,把A拿出来,把A相关的边剪掉,再找下一个入度为0的点,有两个点(B,C),取最左原则,拿B,这是排序是AB,然后剪B相关的边,这时候入度为0的点有E和C,取最左。这时候排序为ABE,接着剪E相关的边,这时只有一个点入度为0,那就是C,取C,顺序为ABEC。剪C的边得到两个入度为0的点(DF),取最左D,顺序为ABECD,然后剪D相关的边,那么下一个入度为0的就是F,然后是object。那么最后的排序就为ABECDFobject。

  1. 对比一下 A.__mro__的结果
  2.  
  3. (<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.F'>, <type 'object'>)

完全正确!

文章写的太透彻啦!!!

试验python3.4.4&&python2.7.11答案一致,均如下

  1. class D(object):
  2. pass
  3.  
  4. class E(object):
  5. pass
  6.  
  7. class F(object):
  8. pass
  9.  
  10. class C(D, F):
  11. pass
  12.  
  13. class B(E, D):
  14. pass
  15.  
  16. class A(B, C):
  17. pass
  18.  
  19. if __name__=='__main__':
  20. print(A.__mro__)

答案:

(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.F'>, <class 'object'>)

【转自】http://www.tuicool.com/articles/EZjmemu

大神的博客:http://www.xymlife.com/2016/05/22/python_mro/

【转】你真的理解Python中MRO算法吗?的更多相关文章

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

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

  2. 理解 Python 中的可变参数 *args 和 **kwargs:

    默认参数:  Python是支持可变参数的,最简单的方法莫过于使用默认参数,例如: def getSum(x,y=5): print "x:", x print "y:& ...

  3. [转]深刻理解Python中的元类(metaclass)以及元类实现单例模式

    使用元类 深刻理解Python中的元类(metaclass)以及元类实现单例模式 在看一些框架源代码的过程中碰到很多元类的实例,看起来很吃力很晦涩:在看python cookbook中关于元类创建单例 ...

  4. 深入理解Python中的yield和send

    send方法和next方法唯一的区别是在执行send方法会首先把上一次挂起的yield语句的返回值通过参数设定,从而实现与生成器方法的交互. 但是需要注意,在一个生成器对象没有执行next方法之前,由 ...

  5. 如何理解python中的if __name__=='main'的作用

    一. 一个浅显易懂的比喻 我们在学习python编程时,不可避免的会遇到if __name__=='main'这样的语句,它到底有什么作用呢? <如何简单地理解Python中的if __name ...

  6. 深入理解Python中的GIL(全局解释器锁)

    深入理解Python中的GIL(全局解释器锁) Python是门古老的语言,要想了解这门语言的多线程和多进程以及协程,以及明白什么时候应该用多线程,什么时候应该使用多进程或协程,我们不得不谈到的一个东 ...

  7. 深入理解python中函数传递参数是值传递还是引用传递

    深入理解python中函数传递参数是值传递还是引用传递 目前网络上大部分博客的结论都是这样的: Python不允许程序员选择采用传值还是传 引用.Python参数传递采用的肯定是"传对象引用 ...

  8. 全面理解python中self的用法

    self代表类的实例,而非类. class Test: def prt(self): print(self) print(self.__class__) t = Test() t.prt() 执行结果 ...

  9. 用python实现MRO算法

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

随机推荐

  1. WCF vs ASMX WebService

    This question comes up a lot in conversations I have with developers. “Why would I want to switch to ...

  2. Gym 100851K

    Problem King's Inspection 题目大意 给一张n个点m条边的无向图,问是否存在一条欧拉回路. n<=10^5, 0<=m<=n+20. 解题分析 注意到数据范围 ...

  3. 国内较快的maven镜像

    原文网址:http://www.cnblogs.com/dingyingsi/p/3856456.html 国内连接maven官方的仓库更新依赖库,网速一般很慢,收集一些国内快速的maven仓库镜像以 ...

  4. Android:给ViewPager添加切换效果

    原文参照开发者官网:http://developer.android.com/training/animation/screen-slide.html#viewpager 以App的引导页为例: 首先 ...

  5. Jena Fuseki 101

    前言 正如其承诺的那样 Expose your triples as a SPARQL end-point accessible over HTTP. Fuseki provides REST-sty ...

  6. linux下locate为什么有时候某些文件

    这与locate命令的工作原理有关.他是通过查询数据库的方式查找文件的.并且数据库每天更新一次.你要找的文件可能没有更新到数据库中. 可以有两种选择: 第一,过一天后再查看一下,应该就会找到了. 第二 ...

  7. Codeforces Round #157 (Div. 2)

    A. Little Elephant and Chess 模拟. B. Little Elephant and Magic Square 枚举左上角,计算其余两个位置的值,在\(3\times 3\) ...

  8. JavaScript对象属性赋值操作的逻辑

    对象进行属性赋值操作时,其执行逻辑如下所示: 1. 当前对象中是否有该属性?有,进行赋值操作:没有,进行下一步判断. 2. 对象的原型链中是否有该属性?没有,在当前对象上创建该属性,并赋值:有,进行下 ...

  9. 网易开发工程师编程题 比较重量 Java

    比较重量 小明陪小红去看钻石,他们从一堆钻石中随机抽取两颗并比较她们的重量.这些钻石的重量各不相同.在他们们比较了一段时间后,它们看中了两颗钻石g1和g2.现在请你根据之前比较的信息判断这两颗钻石的哪 ...

  10. [转]MYSQL远程登录权限设置

    Mysql默认关闭远程登录权限,如下操作允许用户在任意地点登录:   1. 进入mysql,GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED B ...