关于python:新样式类中的方法解析顺序(mro)?

Method Resolution Order (MRO) in new-style classes?

在《简而言之的Python》(第二版)一书中,有一个例子旧样式类来演示如何以经典的解析顺序解析方法,以及和新订单有什么不同?

我用新样式重写了示例,尝试了同样的示例,但是结果与旧样式类得到的结果没有什么不同。我用来运行这个示例的python版本是2.5.2。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Base1(object):  
    def amethod(self): print"Base1"  

class Base2(Base1):  
    pass

class Base3(object):  
    def amethod(self): print"Base3"

class Derived(Base2,Base3):  
    pass

instance = Derived()  
instance.amethod()  
print Derived.__mro__

调用instance.amethod()打印Base1,但根据我对具有新类型类的MRO的理解,输出应该是Base3。调用Derived.__mro__打印:

(, , , , )

我不确定我对新样式类MRO的理解是否错误,或者我正在犯一个我无法察觉的愚蠢错误。请帮助我更好地理解MRO。


当同一祖先类在"幼稚的"深度优先方法中不止一次出现时,传统类和新型类的解决顺序之间的关键区别就出现了,例如,考虑"菱形继承"情况:

1
2
3
4
5
6
7
8
9
10
>>> class A: x = 'a'
...
>>> class B(A): pass
...
>>> class C(A): x = 'c'
...
>>> class D(B, C): pass
...
>>> D.x
'a'

这里,传统样式,解析顺序是d-b-a-c-a:所以当查找d.x时,a是解析的第一个基础,因此在c中隐藏定义。同时:

1
2
3
4
5
6
7
8
9
10
11
>>> class A(object): x = 'a'
...
>>> class B(A): pass
...
>>> class C(A): x = 'c'
...
>>> class D(B, C): pass
...
>>> D.x
'c'
>>>

这里,新款,订单是:

1
2
3
>>> D.__mro__
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>,
    <class '__main__.A'>, <type 'object'>)

由于A被迫在其所有子类之后仅按一次解决顺序出现,因此覆盖(即c对x成员的覆盖)实际上是明智的。

这是应该避免使用旧样式类的原因之一:具有"菱形"模式的多重继承对它们不起作用,而对新样式则起作用。


python的方法解析顺序实际上比仅仅理解菱形模式更复杂。要真正理解它,请看一下c3线性化。我发现在扩展方法跟踪订单时,使用print语句真的很有帮助。例如,您认为这个模式的输出是什么?(注意:"x"假设为两个交叉边,而不是节点,^表示调用super()的方法)

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
class G():
    def m(self):
        print("G")

class F(G):
    def m(self):
        print("F")
        super().m()

class E(G):
    def m(self):
        print("E")
        super().m()

class D(G):
    def m(self):
        print("D")
        super().m()

class C(E):
    def m(self):
        print("C")
        super().m()

class B(D, E, F):
    def m(self):
        print("B")
        super().m()

class A(B, C):
    def m(self):
        print("A")
        super().m()


#      A^
#     / \
#    B^  C^
#   /| X
# D^ E^ F^
#  \ | /
#    G

你拿到B D C E F G了吗?

1
2
x = A()
x.m()

经过多次试错,我提出了一个关于C3线性化的非正式图论解释,如下:(如果是错的,请有人告诉我。)

考虑这个例子:

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
class I(G):
    def m(self):
        print("I")
        super().m()

class H():
    def m(self):
        print("H")

class G(H):
    def m(self):
        print("G")
        super().m()

class F(H):
    def m(self):
        print("F")
        super().m()

class E(H):
    def m(self):
        print("E")
        super().m()

class D(F):
    def m(self):
        print("D")
        super().m()

class C(E, F, G):
    def m(self):
        print("C")
        super().m()

class B():
    def m(self):
        print("B")
        super().m()

class A(B, C, D):
    def m(self):
        print("A")
        super().m()

# Algorithm:

# 1. Build an inheritance graph such that the children point at the parents (you'll have to imagine the arrows are there) and
#    keeping the correct left to right order. (I've marked methods that call super with ^)

#          A^
#       /  |  \
#     /    |    \
#   B^     C^    D^  I^
#        / | \  /   /
#       /  |  X    /  
#      /   |/  \  /    
#    E^    F^   G^
#     \    |    /
#       \  |  /
#          H
# (In this example, A is a child of B, so imagine an edge going FROM A TO B)

# 2. Remove all classes that aren't eventually inherited by A

#          A^
#       /  |  \
#     /    |    \
#   B^     C^    D^
#        / | \  /  
#       /  |  X    
#      /   |/  \
#    E^    F^   G^
#     \    |    /
#       \  |  /
#          H

# 3. For each level of the graph from bottom to top
#       For each node in the level from right to left
#           Remove all of the edges coming into the node except for the right-most one
#           Remove all of the edges going out of the node except for the left-most one

# Level {H}
#
#          A^
#       /  |  \
#     /    |    \
#   B^     C^    D^
#        / | \  /  
#       /  |  X    
#      /   |/  \
#    E^    F^   G^
#               |
#               |
#               H

# Level {G F E}
#
#         A^
#       / |  \
#     /   |    \
#   B^    C^   D^
#         | \ /  
#         |  X    
#         | | \
#         E^F^ G^
#              |
#              |
#              H

# Level {D C B}
#
#      A^
#     /| \
#    / |  \
#   B^ C^ D^
#      |  |  
#      |  |    
#      |  |  
#      E^ F^ G^
#            |
#            |
#            H

# Level {A}
#
#   A^
#   |
#   |
#   B^  C^  D^
#       |   |
#       |   |
#       |   |
#       E^  F^  G^
#               |
#               |
#               H

# The resolution order can now be determined by reading from top to bottom, left to right.  A B C E D F G H

x = A()
x.m()


你得到的结果是正确的。尝试将Base3的基类更改为Base1,并与经典类的相同层次结构进行比较:

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
class Base1(object):
    def amethod(self): print"Base1"

class Base2(Base1):
    pass

class Base3(Base1):
    def amethod(self): print"Base3"

class Derived(Base2,Base3):
    pass

instance = Derived()
instance.amethod()


class Base1:
    def amethod(self): print"Base1"

class Base2(Base1):
    pass

class Base3(Base1):
    def amethod(self): print"Base3"

class Derived(Base2,Base3):
    pass

instance = Derived()
instance.amethod()

现在它输出:

1
2
Base3
Base1

阅读此解释了解更多信息。


您看到这种行为是因为方法解析是深度优先,而不是宽度优先。德维德的遗产看起来像

1
2
3
         Base2 -> Base1
        /
Derived - Base3

所以instance.amethod()

  • 检查base2,找不到amethod。
  • 看到base2从base1继承,并检查base1。base1有一个amethod,所以它被调用。
  • 这反映在Derived.__mro__中。只需遍历Derived.__mro__并在找到要查找的方法时停止。