12 Python MRO

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

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

经典类,MRO=深度优先搜索
经典类是一种不能继承的类,如果经典类被作为父类,子类调用父类的构造函数则会出错.

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

经典类没有__mro__属性;

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

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

正常继承:

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

菱形继承:

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
1
2
3
4
5
6
新式类采用C3算法求解mro,并且新式类是有__mro__属性的.

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

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

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

正常继承:

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

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

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

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

菱形继承:

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

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

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

代码实现:

#!/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中找到
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
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:

找到入度为0的点,只有A,将A加入mro顺序列表中,剪掉与A相连的边;
再找入度为0的点,有两个点B和C,取最左,so将B加入mro顺序列表中,剪掉与B相连的边;
再找入度为0的点,有两个点D和C,取最左,so将D加入mro顺序列表中,剪掉与D相连的边;
再找入度为0的点,只有C,so将C加入mro顺序列表中,剪掉与C相连的边;
再找入度为0的点,只有E,so将E加入mro顺序列表中;
result: mro = [A,B,D,C,E] 正确

菱形继承:

找到入度为0的点,只有A,将A加入mro顺序列表中,剪掉与A相连的边;
再找入度为0的点,有两个点B和C,取最左,so将B加入mro顺序列表中,剪掉与B相连的边;
再找入度为0的点,只有C,so将C加入mro顺序列表中,剪掉与C相连的边;
再找入度为0的点,只有D,so将D加入mro顺序列表中;
result: mro = [A,B,C,D] 正确

比较BFS,DFS,C3求解mro


算法    正常继承 mro顺序    菱形继承 mro顺序
DFS    A,B,D,C,E    A,B,D,C (只能继承,不能重写问题)
BFS    A,B,C,D,E(不满足单调性原则)    A,B,C,D
C3    A,B,D,C,E    A,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'>)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
上述代码的继承关系图,继承顺序为从左到右:

利用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的点,然后减去相连的边,不停重复,直至只剩下一个点)

找到入度为0的点,只有A,将A加入mro顺序列表中,剪掉与A相连的边;
再找入度为0的点,有两个点B和C,取最左,so将B加入mro顺序列表中,剪掉与B相连的边;
再找入度为0的点,有两个点E和C,取最左,so将E加入mro顺序列表中,剪掉与E相连的边;
再找入度为0的点,只有C,so将C加入mro顺序列表中,剪掉与C相连的边;
再找入度为0的点,有两个点D和F,取最左,so将D加入mro顺序列表中,剪掉与D相连的边;
再找入度为0的点,只有F,so将F加入mro顺序列表中,剪掉与F相连的边;
只剩下object,将object加入mro顺序列表中;
此时得出mro = [A,B,E,C,D,F,O],与程序结果一致.

总结
mro的计算不必深究,了解就好,在实际应用时,直接使用class.__mro__查看方法解析顺序即可.另外在开发中要尽量避免多重继承,不要混用经典类和新式类……
--------------------- 
作者:忧桑的小兔子 
来源:CSDN 
原文:https://blog.csdn.net/lis_12/article/details/52859376 
版权声明:本文为博主原创文章,转载请附上博文链接!

猜你喜欢

转载自blog.csdn.net/f110300641/article/details/85030124
MRO