新式类中的方法解析顺序(MRO)?
- 2025-01-14 08:50:00
- admin 原创
- 106
问题描述:
在《Python 简介(第 2 版)》一书中,有一个例子使用
旧式类来演示如何按照经典解析顺序解析方法,以及
与新顺序有何不同。
我尝试用新样式重写该示例,但结果与使用旧样式类获得的结果没有什么不同。我用来运行示例的 Python 版本是2.5.2。以下是示例:
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__
打印:
(<class '__main__.Derived'>, <class '__main__.Base2'>, <class '__main__.Base1'>, <class '__main__.Base3'>, <type 'object'>)
我不确定我对新式类 MRO 的理解是否不正确,或者我犯了一个我无法发现的愚蠢错误。请帮助我更好地理解 MRO。
解决方案 1:
遗留类和新式类的解析顺序之间的关键区别在于,当同一个祖先类在“朴素”的深度优先方法中出现多次时 - 例如,考虑“菱形继承”的情况:
>>> 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 :因此,当查找 Dx 时,A 是按照解析顺序第一个解决它的基数,从而隐藏了 C 中的定义。而:
>>> class A(object): x = 'a'
...
>>> class B(A): pass
...
>>> class C(A): x = 'c'
...
>>> class D(B, C): pass
...
>>> D.x
'c'
>>>
这里,新风格的顺序是:
>>> D.__mro__
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>,
<class '__main__.A'>, <type 'object'>)
强制A
按照解析顺序仅出现一次,并且在其所有子类之后,以便覆盖(即,C 的成员覆盖x
)实际上可以合理地工作。
这是应该避免使用旧式类的原因之一:具有“菱形”模式的多重继承对它们不起作用,但对新式类却起作用。
解决方案 2:
Python 的方法解析顺序实际上比仅仅理解菱形模式更复杂。要真正理解它,请查看C3 线性化。我发现在扩展方法以跟踪顺序时使用打印语句确实很有帮助。例如,您认为此模式的输出是什么?(注意:'X' 应该是两个交叉边,而不是节点,^ 表示调用 super() 的方法)
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^
# / \n# B^ C^
# /| X
# D^ E^ F^
# | /
# G
您得到 ABDCEFG 了吗?
x = A()
x.m()
经过多次反复试验后,我得出了 C3 线性化的非正式图论解释,如下所示:(如果这是错误的,请告诉我。)
考虑这个例子:
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^
# / | \n# / | \n# 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^
# / | \n# / | \n# 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^
# / | \n# / | \n# B^ C^ D^
# / | /
# / | X
# / |/
# E^ F^ G^
# |
# |
# H
# Level {G F E}
#
# A^
# / | \n# / | \n# B^ C^ D^
# | /
# | X
# | | \n# E^F^ G^
# |
# |
# H
# Level {D C B}
#
# A^
# /| \n# / | \n# 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()
解决方案 3:
得到的结果是正确的。尝试将 的基类更改Base3
为Base1
,并与经典类的相同层次结构进行比较:
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()
现在输出:
Base3
Base1
阅读此解释以获取更多信息。
解决方案 4:
您之所以看到这种行为,是因为方法解析是深度优先,而不是广度优先。Dervied 的继承如下所示
Base2 -> Base1
/
Derived - Base3
所以instance.amethod()
检查 Base2,没有找到方法。
看到 Base2 继承自 Base1,并检查 Base1。Base1 有一个
amethod
,因此被调用。
这反映在 中Derived.__mro__
。只需迭代Derived.__mro__
并在找到所寻找的方法时停止。