新样式类中的方法解析顺序(MRO) ?

时间:2022-02-03 17:02:48

In the book Python in a Nutshell (2nd Edition) there is an example which uses
old style classes to demonstrate how methods are resolved in classic resolution order and
how is it different with the new order.

在《Python简而言之(第二版)》一书中,有一个例子使用旧样式类来演示如何以经典的分辨率顺序解析方法,以及它与新顺序有何不同。

I tried the same example by rewriting the example in new style but the result is no different than what was obtained with old style classes. The python version I am using to run the example is 2.5.2. Below is the example:

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

class Base1(object):      def amethod(self): print "Base1"  class Base2(Base1):      passclass Base3(object):      def amethod(self): print "Base3"class Derived(Base2,Base3):      passinstance = Derived()  instance.amethod()  print Derived.__mro__  

The call instance.amethod() prints Base1, but as per my understanding of the MRO with new style of classes the output should have been Base3. The call Derived.__mro__ prints:

调用实例。amethod()打印Base1,但是根据我对MRO的理解,使用新的类样式,输出应该是Base3。调用派生。__mro__打印:

(<class '__main__.Derived'>, <class '__main__.Base2'>, <class '__main__.Base1'>, <class '__main__.Base3'>, <type 'object'>)

( <类的__main__。派生的__main__> 、 <类。base2 __main__> 、 <类。base1 __main__> 、 <类。base3> , <类型“对象”> )

I am not sure if my understanding of MRO with new style classes is incorrect or that I am doing a silly mistake which I am not able to detect. Please help me in better understanding of MRO.

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

4 个解决方案

#1


146  

The crucial difference between resolution order for legacy vs new-style classes comes when the same ancestor class occurs more than once in the "naive", depth-first approach -- e.g., consider a "diamond inheritance" case:

遗留类与新样式类的解析顺序的关键区别在于,在“朴素”、深度优先的方法中,同一祖先类出现的次数不止一次——例如,考虑“钻石继承”的情况:

>>> class A: x = 'a'... >>> class B(A): pass... >>> class C(A): x = 'c'... >>> class D(B, C): pass... >>> D.x'a'

here, legacy-style, the resolution order is D - B - A - C - A : so when looking up D.x, A is the first base in resolution order to solve it, thereby hiding the definition in C. While:

这里是legacy-style,分辨率顺序是D - B - A - C - A:所以当查找D时。x, A是求解它的第一个基,从而将定义隐藏在c中。

>>> class A(object): x = 'a'... >>> class B(A): pass... >>> class C(A): x = 'c'... >>> class D(B, C): pass... >>> D.x'c'>>> 

here, new-style, the order is:

在这里,new-style,订单是:

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

with A forced to come in resolution order only once and after all of its subclasses, so that overrides (i.e., C's override of member x) actually work sensibly.

它的所有子类都被强制以分辨率的顺序出现一次,然后覆盖(例如。,C的覆盖成员x)实际上是合理的。

It's one of the reasons that old-style classes should be avoided: multiple inheritance with "diamond-like" patterns just doesn't work sensibly with them, while it does with new-style.

这是应该避免使用旧式类的原因之一:使用“类钻形”模式的多重继承不能合理地使用它们,而使用新样式则可以。

#2


16  

Python's method resolution order is actually more complex than just understanding the diamond pattern. To really understand it, take a look at C3 linearization. I've found it really helps to use print statements when extending methods to track the order. For example, what do you think the output of this pattern would be? (Note: the 'X' is suppose to be two crossing edges, not a node and ^ signifies methods that call super())

Python的方法解析顺序实际上比理解diamond模式要复杂得多。要真正理解它,请看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

Did you get A B D C E F G?

你得到了一个B D C E F G吗?

x = A()x.m()

After a lot of trial an error, I came up with an informal graph theory interpretation of C3 linearization as follows: (Someone please let me know if this is wrong.)

经过大量的尝试和错误,我提出了一个非正式的图形理论解释C3线性化如下:(请让我知道这是错误的)

Consider this example:

考虑一下这个例子:

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 Hx = A()x.m()

#3


5  

The result you get is correct. Try changing base class of Base3 to Base1 and compare with the same hierarchy for classic classes:

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

class Base1(object):    def amethod(self): print "Base1"class Base2(Base1):    passclass Base3(Base1):    def amethod(self): print "Base3"class Derived(Base2,Base3):    passinstance = Derived()instance.amethod()class Base1:    def amethod(self): print "Base1"class Base2(Base1):    passclass Base3(Base1):    def amethod(self): print "Base3"class Derived(Base2,Base3):    passinstance = Derived()instance.amethod()

Now it outputs:

现在输出:

Base3Base1

Read this explanation for more information.

更多信息请阅读下面的解释。

#4


0  

You're seeing that behavior because method resolution is depth-first, not breadth-first. Dervied's inheritance looks like

您看到这种行为是因为方法解析是深度优先的,而不是广度优先的。继承的推导出脉动风速及风压的样子

         Base2 -> Base1        /Derived - Base3

So instance.amethod()

因此instance.amethod()

  1. Checks Base2, doesn't find amethod.
  2. 检查Base2,没有找到amethod。
  3. Sees that Base2 has inherited from Base1, and checks Base1. Base1 has a amethod, so it gets called.
  4. 看到Base2继承了Base1,并检查Base1。Base1有一个amethod,所以它被调用。

This is reflected in Derived.__mro__. Simply iterate over Derived.__mro__ and stop when you find the method being looked for.

这反映在嘲笑中。简单的遍历。找到要查找的方法时停止。

#1


146  

The crucial difference between resolution order for legacy vs new-style classes comes when the same ancestor class occurs more than once in the "naive", depth-first approach -- e.g., consider a "diamond inheritance" case:

遗留类与新样式类的解析顺序的关键区别在于,在“朴素”、深度优先的方法中,同一祖先类出现的次数不止一次——例如,考虑“钻石继承”的情况:

>>> class A: x = 'a'... >>> class B(A): pass... >>> class C(A): x = 'c'... >>> class D(B, C): pass... >>> D.x'a'

here, legacy-style, the resolution order is D - B - A - C - A : so when looking up D.x, A is the first base in resolution order to solve it, thereby hiding the definition in C. While:

这里是legacy-style,分辨率顺序是D - B - A - C - A:所以当查找D时。x, A是求解它的第一个基,从而将定义隐藏在c中。

>>> class A(object): x = 'a'... >>> class B(A): pass... >>> class C(A): x = 'c'... >>> class D(B, C): pass... >>> D.x'c'>>> 

here, new-style, the order is:

在这里,new-style,订单是:

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

with A forced to come in resolution order only once and after all of its subclasses, so that overrides (i.e., C's override of member x) actually work sensibly.

它的所有子类都被强制以分辨率的顺序出现一次,然后覆盖(例如。,C的覆盖成员x)实际上是合理的。

It's one of the reasons that old-style classes should be avoided: multiple inheritance with "diamond-like" patterns just doesn't work sensibly with them, while it does with new-style.

这是应该避免使用旧式类的原因之一:使用“类钻形”模式的多重继承不能合理地使用它们,而使用新样式则可以。

#2


16  

Python's method resolution order is actually more complex than just understanding the diamond pattern. To really understand it, take a look at C3 linearization. I've found it really helps to use print statements when extending methods to track the order. For example, what do you think the output of this pattern would be? (Note: the 'X' is suppose to be two crossing edges, not a node and ^ signifies methods that call super())

Python的方法解析顺序实际上比理解diamond模式要复杂得多。要真正理解它,请看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

Did you get A B D C E F G?

你得到了一个B D C E F G吗?

x = A()x.m()

After a lot of trial an error, I came up with an informal graph theory interpretation of C3 linearization as follows: (Someone please let me know if this is wrong.)

经过大量的尝试和错误,我提出了一个非正式的图形理论解释C3线性化如下:(请让我知道这是错误的)

Consider this example:

考虑一下这个例子:

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 Hx = A()x.m()

#3


5  

The result you get is correct. Try changing base class of Base3 to Base1 and compare with the same hierarchy for classic classes:

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

class Base1(object):    def amethod(self): print "Base1"class Base2(Base1):    passclass Base3(Base1):    def amethod(self): print "Base3"class Derived(Base2,Base3):    passinstance = Derived()instance.amethod()class Base1:    def amethod(self): print "Base1"class Base2(Base1):    passclass Base3(Base1):    def amethod(self): print "Base3"class Derived(Base2,Base3):    passinstance = Derived()instance.amethod()

Now it outputs:

现在输出:

Base3Base1

Read this explanation for more information.

更多信息请阅读下面的解释。

#4


0  

You're seeing that behavior because method resolution is depth-first, not breadth-first. Dervied's inheritance looks like

您看到这种行为是因为方法解析是深度优先的,而不是广度优先的。继承的推导出脉动风速及风压的样子

         Base2 -> Base1        /Derived - Base3

So instance.amethod()

因此instance.amethod()

  1. Checks Base2, doesn't find amethod.
  2. 检查Base2,没有找到amethod。
  3. Sees that Base2 has inherited from Base1, and checks Base1. Base1 has a amethod, so it gets called.
  4. 看到Base2继承了Base1,并检查Base1。Base1有一个amethod,所以它被调用。

This is reflected in Derived.__mro__. Simply iterate over Derived.__mro__ and stop when you find the method being looked for.

这反映在嘲笑中。简单的遍历。找到要查找的方法时停止。