12 Python MRO

Python MRO(方法解析顺序)

转载请标明出处(http://blog.csdn.net/lis_12/article/details/52859376).

MRO (Method Resolution Order)

Python在多重继承时,如果父类中存在同名函数会产生二义性,Python中处理这个问题方法就是MRO.

如果对MRO发展史没兴趣的话,直接看C3算法.

经典类,MRO=深度优先搜索

经典类是一种不能继承的类,如果经典类被作为父类,子类调用父类的构造函数则会出错.

经典类的MRO采用 “深度优先搜索“,子节点顺序从左到右;

经典类没有__mro__属性;

当出现菱形继承时,深度优先搜索就会出现问题.下面给出了正常继承模式和菱形继承模式下,采用DFS得出的MRO:

<画继承关系图时,要注意继承顺序,先继承的在最左侧,谨记这点,继承顺序不同,mro也是不一样的>

dfs

  1. 正常继承:

    如果两个互不相关的类多重继承,此时MRO正常,不会引起任何问题;

  2. 菱形继承:

    B和C有公共父类D,假设C重写了D中的fun()方法,按照MRO顺序先找到D中的fun()方法,此时C中重写的fun()方法将永远访问不到,导致了C只能继承不能重写D中的方法(即使C重写了fun()方法也不会访问到),这就是深度优先搜索的缺陷;

新式类,MRO = C3

为了使类和内置类型更加统一,解决经典类中多重继承只能继承不能重写的问题,引入了新式类.新式类的每个类都继承于一个基类(object),可以是自定义类或其他类,默认继承于object.子类可以调用父类的构造函数.

#经典类
class A():
    pass
#新式类
class A(object):
    pass

新式类采用C3算法求解mro,并且新式类是有__mro__属性的.

BFS,广度优先搜索

在介绍C3算法时,有必要了解BFS.如果新式类的MRO采用BFS(广度优先搜索,子节点顺序从左到右)

当出现正常继承时,广度优先搜索就会出现问题.下面给出了正常继承模式和菱形继承模式下,采用BFS得出的MRO:

<画继承关系图时,要注意继承顺序,先继承的在最左侧,谨记这点,继承顺序不同,mro也是不一样的>

bfs

  1. 正常继承:

    如果两个互不相关的类多重继承,则会发生问题.

    假设:D中有foo()方法,C也有foo()方法,则按照MRO顺序,应该是先在B中查找,再在C中查找.

    正常情况下,如果在B中未找到的话,应该在B的父类中查找,而不是在C中查找.

    这种先在B中查找然后再D中查找的顺序,称为单调性.(这就像继承遗产一样,如果继承人的儿子媳妇不在了,要先找继承人的孙子,孙女啊,而不是找什么侄子,外甥什么的…..)

  2. 菱形继承:

    解决了深度优先搜索出现的只能继承不能重写问题,但是违反了继承的单调性原则.

C3算法

下面开始正题,从Python2.3开始,新式类的MRO采用C3算法,解决了单调性和只能继承无法重写的问题.

MRO采用C3算法后,此时上述两种继承模式的MRO:

C3

代码实现:

#!/usr/bin/python
# -*- coding: utf-8 -*-
#正常继承.py
class D(object):
    def __init__(self):
        super(D,self).__init__()
        print 'D'

    def fun(self):
        print 'D fun()'

class E(object):
    def __init__(self):
        super(E,self).__init__()
        print 'E'

class C(E):
    def __init__(self):
        super(C,self).__init__()
        print 'C'
    def fun(self):
        print 'C fun()'

class B(D):
    def __init__(self):
        super(B,self).__init__()
        print 'B'

class A(B,C):
    def __init__(self):
        super(A,self).__init__()
        print 'A'

if __name__ == '__main__':#主程序
    a = A()  #E C D B A
    print A.__mro__#(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.D'>, <class '__main__.C'>, <class '__main__.E'>, <type 'object'>)  正确
    a.fun()  #D fun() 按照MRO搜索fun()方法,在D中找到

#菱形继承.py
#!/usr/bin/python
# -*- coding: utf-8 -*-

class D(object):
    def __init__(self):
        super(D,self).__init__()
        print 'D'

    def fun(self):
        print 'D fun()'

class C(D):
    def __init__(self):
        super(C,self).__init__()
        print 'C'

    def fun(self):
        print 'C fun()'

class B(D):
    def __init__(self):
        super(B,self).__init__()
        print 'B'

class A(B,C):
    def __init__(self):
        super(A,self).__init__()
        print 'A'

if __name__ == '__main__':#主程序
    a = A()  #DCBA
    print A.__mro__ #(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <type 'object'>)   正确
    a.fun()  #C fun()  按照MRO搜索fun()方法,在C中找到

C3算法成功解决了单调性和只能继承无法重写的问题.下面来看下神奇的C3算法是如何计算mro的.

C3算法计算访问顺序列表

merge list公式法

C3算法的核心就是merge.

在merge列表中,如果第一个序列mro的第一个类出现在其它序列,并且也是第一个,或者某个类仅在一个序列中存在,其它序列不存在,那么这个类就会从这些序列中删除,并合到访问顺序列表中,如果在第一个序列中未找到满足上述条件的类,则在第二个序列开始查找,如果在第二个中也找不到符合条件的,则在第三个序列中查找……

谨记:mro(object) = [O]

如:class B(A1,A2,A3 …) 继承顺序也很重要,不要忽略了

此时B的mro序列 mro(B) = [B] + merge(mro(A1), mro(A2), mro(A3) …, [A1,A2,A3])

merge中的排列要严格遵守继承顺序.

以正常继承模式的几个类为例:(O为object类,新式类为object的子类)

正常继承

mro(D) = [D,O]

mro(E) = [E,O]

mro(B) = [B] + merge(mro(D) ,[D]) = [B] + merge([D,O] + [D]) = [B,D] +merge([O]) = [B,D,O]

同理:mro(C) = [C,E,O]

mro(A) = [A] + merge(mro(B),mro(C),[B,C])

​ = [A] + merge([B,D,O],[C,E,O],[B,C]) #B为第一个序列的第一个类,并且B在第三个序列中也是第一个类,合并

​ = [A,B] + merge([D,O],[C,E,O],[C]) #D仅在第一个序列中存在,合并

​ = [A,B,D] + merge([O],[C,E,O],[C]) #第一个序列中O不满足任何条件,在第二个序列中查找,C为第二个序

​ 的第一个类,并且也为第三个序列的第一个类,合并

​ = [A,B,D,C] + merge([O],[E,O]) #第一个序列中的O不满足任何条件,在第二个序列中查找,E仅在第二

​ 个序列中存在,合并

​ = [A,B,D,C,E] + merge([O],[O]) #O为第一个序列的第一个类,也是第二个序列的第一个类,合并

​ = [A,B,D,C,E,O]
result: [A,B,D,C,E,O] 正确

拓扑排序求解mro

解决单调性(BFS正常继承出现的问题):只要保证从根到叶,从左到右的访问顺序即可;

只能继承,不能重写(DFS菱形继承出现的问题):主要是因为先访问了子类的父类导致的.

如果熟悉图论的话,应该能想到拓扑排序能很好的解决上述问题.

拓扑排序:

对于一个有向无环图G进行拓扑排序,就是将G中所有的顶点排成一个线性序列,使得图中任意一对顶点u和v,若边(u,v)属于E(G),则u在线性序列中出现在v之前.通常,这样的线性序列称之为满足拓扑次序的序列,简称拓扑排序.

因为拓扑排序肯定是由根节点到叶节点,所以只要满足从左到右的原则,得到的拓扑排序就是mro.

以正常继承模式和菱形继承模式的几个类为例,用拓扑排序求解mro:

正常继承

  1. 找到入度为0的点,只有A,将A加入mro顺序列表中,剪掉与A相连的边;
  2. 再找入度为0的点,有两个点B和C,取最左,so将B加入mro顺序列表中,剪掉与B相连的边;
  3. 再找入度为0的点,有两个点D和C,取最左,so将D加入mro顺序列表中,剪掉与D相连的边;
  4. 再找入度为0的点,只有C,so将C加入mro顺序列表中,剪掉与C相连的边;
  5. 再找入度为0的点,只有E,so将E加入mro顺序列表中;

result: mro = [A,B,D,C,E] 正确

菱形继承:

这里写图片描述

  1. 找到入度为0的点,只有A,将A加入mro顺序列表中,剪掉与A相连的边;
  2. 再找入度为0的点,有两个点B和C,取最左,so将B加入mro顺序列表中,剪掉与B相连的边;
  3. 再找入度为0的点,只有C,so将C加入mro顺序列表中,剪掉与C相连的边;
  4. 再找入度为0的点,只有D,so将D加入mro顺序列表中;

result: mro = [A,B,C,D] 正确

比较BFS,DFS,C3求解mro

菱形继承

算法正常继承 mro顺序菱形继承 mro顺序
DFSA,B,D,C,EA,B,D,C (只能继承,不能重写问题)
BFSA,B,C,D,E(不满足单调性原则)A,B,C,D
C3A,B,D,C,EA,B,C,D

实战计算mro

#!/usr/bin/python
# -*- coding: utf-8 -*-
class E(object):
    pass

class D(object):
    pass

class F(object):
    pass

class C(D,F):
    pass

class B(E,D):
    pass

class A(B,C):
    pass

if __name__ == '__main__':
    print A.__mro__#(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.F'>, <type 'object'>)

上述代码的继承关系图,继承顺序为从左到右:

继承关系图

利用merge计算A的mro:(O代表object类)

mro(E) = [E,O]

mro(D) = [D,O]

mro(F) = [F,O]

mro(B) = [B] + merge(mro(E),mro(D),[E,D])

​ = [B] + merge([E,O],[D,O],[E,D]) E符合merge条件

​ = [B,E] + merge([O],[D,O],[D]) D符合merge条件

​ = [B,E,D] + merge([O],[O],[]) O符合merge条件

​ = [B,E,D,O]

mro(C) = [C] + merge(mro(D),mro(F),[D,F])

​ = [C] + merge([D,O],[F,O],[D,F]) D符合merge条件

​ = [C,D] + merge([O],[F,O],[F]) F符合merge条件

​ = [C,D,F] + merge([O],[O],[]) O符合merge条件

​ = [C,D,F,O]

mro(A) = [A] + merge(mro(B),mro(C),[B,C])

​ = [A] + merge([B,E,D,O] ,[C,D,F,O] ,[B,C]) B符合merge条件

​ = [A,B] + merge([E,D,O] ,[C,D,F,O] ,[C]) E符合merge条件

​ = [A,B,E] + merge([D,O] ,[C,D,F,O] ,[C]) C符合merge条件

​ = [A,B,E,C] + merge([D,O] ,[D,F,O] ,[]) D符合merge条件

​ = [A,B,E,C,D] + merge([O] ,[F,O] ,[]) F符合merge条件

​ = [A,B,E,C,D,F] + merge([O] ,[O] ,[]) O符合merge条件

​ = [A,B,E,C,D,F,O] 与程序结果一致

利用拓扑排序计算mro

根据继承关系图,(简单来说就是不停找入度为0的点,然后减去相连的边,不停重复,直至只剩下一个点)

  1. 找到入度为0的点,只有A,将A加入mro顺序列表中,剪掉与A相连的边;
  2. 再找入度为0的点,有两个点B和C,取最左,so将B加入mro顺序列表中,剪掉与B相连的边;
  3. 再找入度为0的点,有两个点E和C,取最左,so将E加入mro顺序列表中,剪掉与E相连的边;
  4. 再找入度为0的点,只有C,so将C加入mro顺序列表中,剪掉与C相连的边;
  5. 再找入度为0的点,有两个点D和F,取最左,so将D加入mro顺序列表中,剪掉与D相连的边;
  6. 再找入度为0的点,只有F,so将F加入mro顺序列表中,剪掉与F相连的边;
  7. 只剩下object,将object加入mro顺序列表中;

此时得出mro = [A,B,E,C,D,F,O],与程序结果一致.

总结

mro的计算不必深究,了解就好,在实际应用时,直接使用class.__mro__查看方法解析顺序即可.另外在开发中要尽量避免多重继承,不要混用经典类和新式类……

参考网址

  1. (http://xymlife.com/2016/05/22/python_mro/)
  2. (https://segmentfault.com/a/1190000000749061)
  3. (http://www.cnblogs.com/lovemo1314/archive/2011/05/03/2035005.html)
  4. (http://www.cnblogs.com/i2u9/archive/2013/03/19/pythonmroc3.html)​
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值