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


94

在《Nutshell的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。

Answers:


183

在“天真”的深度优先方法中,同一祖先类出现多次时,旧类与新类的解析顺序之间的关键区别就出现了:例如,考虑“钻石继承”情况:

>>> 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被迫来到分辨率顺序只有一次,毕竟它的子类,从而使覆盖(即尺寸材料的的覆盖x)实际上有效地工作。

这是应避免使用旧式类的原因之一:“菱形”模式的多重继承对它们而言并不明智,而对新式则可行。


2
“ [祖先类] A被强制以仅在其所有子类之后的解析顺序出现,因此,替代(即C对成员x的替代)实际上是明智的。” - 主显节!多亏了这句话,我才可以再次进行MRO。\ o /非常感谢。
Esteis 2015年

23

实际上,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^
#     / \
#    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^
#       /  |  \
#     /    |    \
#   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()

您应该更正您的第二个代码:您已经将类“ I”作为第一行,并且也使用了super,因此它找到了“ G”超类,但是“ I”是第一类,因此它将永远无法找到“ G”类。不是“ G”上方的“ I”。将“ I”类放在“ G”和“ F”之间:)
Aaditya Ura

示例代码不正确。super具有必需的参数。
丹尼

2
在类定义中,super()不需要参数。见https://docs.python.org/3/library/functions.html#super

您的图论不必要地复杂。在第1步之后,将左边的类的边缘插入到右边的类(在任何继承列表中),然后进行拓扑排序即可
凯文(Kevin)

@Kevin我认为那是不对的。按照我的示例,ACDBEFGH不是有效的拓扑排序吗?但这不是解决顺序。

5

您得到的结果是正确的。尝试更改Base3to的基类,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

阅读此说明以获取更多信息。


1

您会看到这种现象,因为方法解析是深度优先的,而不是广度优先的。Dervied的继承看起来像

         Base2 -> Base1
        /
Derived - Base3

所以 instance.amethod()

  1. 检查Base2,找不到方法。
  2. 确认Base2已从Base1继承,并检查Base1。Base1有一个amethod,因此被调用。

这反映在中Derived.__mro__Derived.__mro__找到要查找的方法时,只需简单地迭代并停止。


我怀疑我得到“ Base1”作为答案的原因是因为方法分辨率是深度优先的,我认为它比深度优先的方法还多。请参阅Denis的示例,如果它是深度优先,则o / p应该为“ Base1”。还请参见您提供的链接中的第一个示例,其中还显示了MRO,该MRO表示方法分辨率不仅仅是通过深度优先顺序遍历来确定的。
sateesh

抱歉,Denis提供了有关MRO的文档的链接。请检查一下,我误认为您提供了python.org的链接。
sateesh

4
通常,它是深度优先的,但正如Alex所解释的,有一些聪明的方法可以处理类似钻石的继承。
jamessan
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.