新式课堂中的方法解决顺序(MRO)?

在“ Python in a Nutshell(第2版)”中,有一个使用的例子
旧样式类来演示如何在经典的parsing顺序中解决方法
与新订单有何不同?

我通过重写新样式的例子来尝试相同的例子,但是结果与用旧样式类获得的结果没有什么不同。 我用来运行该示例的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。

旧式和新式类的parsing顺序之间的关键区别在于,相同的祖先类在“天真”,深度优先的方法中出现了不止一次 – 例如,考虑一个“钻石inheritance”的情况:

 >>> class A: x = 'a' ... >>> class B(A): pass ... >>> class C(A): x = 'c' ... >>> class D(B, C): pass ... >>> Dx '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 ... >>> Dx 'c' >>> 

这里是新式的,顺序是:

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

A强制进入parsing顺序只有一次,并在其所有的子类,所以覆盖(即C的成员x覆盖)实际上是明智的工作。

这就是为什么要避免使用旧式的原因之一:“类似钻石”模式的多重inheritance不能与他们合理地工作,而与新式相关。

Python的方法parsing顺序实际上比理解菱形模式更复杂。 要真正理解它,看看C3线性化 。 我发现在扩展方法跟踪订单时使用print语句确实有帮助。 例如,你认为这种模式的输出是什么? (注:'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^ # / \ # B^ C^ # /| X # D^ E^ F^ # \ | / # G 

你有没有得到ABDCEFG?

 x = A() xm() 

经过大量的试验,我发现一个非线性图论的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^ # / | \ # / | \ # 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 {GFE} # # A^ # / | \ # / | \ # B^ C^ D^ # | \ / # | X # | | \ # E^F^ G^ # | # | # H # Level {DCB} # # 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. ABCEDFGH x = A() xm() 

你得到的结果是正确的。 尝试将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 

阅读这个解释了解更多信息。

您会看到这种行为,因为方法parsing是深度优先,而不是宽度优先。 Dervied的inheritance看起来像

  Base2 -> Base1 / Derived - Base3 

所以instance.amethod()

  1. 检查Base2,没有find方法。
  2. 看到Base2已从Base1inheritance,并检查Base1。 Base1有一个amethod ,所以被调用。

这反映在Derived.__mro__ 。 只需遍历Derived.__mro__并在find要查找的方法时停止。