首页 文章

Python的“超级”如何做正确的事情?

提问于
浏览
49

我熟悉Python的MRO; 's not my question. I'好奇如何从super返回的对象实际上设法与父类中的super调用正确的顺序进行通信 . 考虑这个示例代码:

#!/usr/bin/python

class A(object):
    def __init__(self): print "A init"

class B(A):
    def __init__(self):
        print "B init"
        super(B, self).__init__()

class C(A):
    def __init__(self):
        print "C init"
        super(C, self).__init__()

class D(B, C):
    def __init__(self):
        print "D init"
        super(D, self).__init__()

x = D()

代码做直观的事情,它打印:

D init
B init
C init
A init

但是,如果你在B的init函数中注释掉对super的调用,则不会调用A和C的init函数 . 这意味着B对super的调用在某种程度上意识到C在整个类层次结构中的存在 . 我知道super返回一个带有重载get运算符的代理对象,但是在D的init定义中super返回的对象如何将C的存在传达给B的init定义中super返回的对象?后续超级使用调用的信息是否存储在对象本身上?如果是这样,为什么不是超级而不是self.super?

编辑:Jekke非常正确地指出它也是该类的一个属性!您可以在解释器中通过创建两个类A和B来测试它,其中B继承自A,并调用 dir(B) . 它没有 super__super__ 属性 .

5 回答

  • 15

    我在下面提供了一系列链接,它们比我希望的更详细,更准确地回答您的问题 . 不过,我会用自己的话回答你的问题,为你节省一些时间 . 我会说点 -

    • super是内置函数,不是属性 .

    • Python中的每个类型(类)都有一个 __mro__ 属性,用于存储该特定实例的方法解析顺序 .

    • 每次调用super的形式都是super(type [,object-or-type]) . 让我们假设第二个属性是当下的对象 .

    • 在超级调用的起始点,该对象属于Derived类的类型( say DC ) .

    • super在指定为第一个参数的类(在本例中为DC之后的类)之后,在MRO的类中查找匹配(在您的情况下为 __init__ )的方法 .

    • 当找到匹配方法时(例如在类 BC1 中),将调用它 .
      (这个方法应该使用super,所以我假设它 - 请参阅Python 's super is nifty but can' t使用 - 链接如下)然后该方法导致在对象's class' MRO中搜索下一个方法,在 BC1 的右侧 .

    • 冲洗重复洗涤,直到找到并调用所有方法 .

    Explanation for your example

    MRO: D,B,C,A,object
    
    • super(D, self).__init__() 被调用 . isinstance(self,D)=>真

    • 在D右侧的 class 中搜索MRO中的 next method .

    B.__init__ 找到并打电话


    • B.__init__ 来电 super(B, self).__init__() .

    isinstance(self,B)=> False
    isinstance(self,D)=>真

    • 因此,MRO是相同的,但搜索继续到B的右边,即C,A,逐个搜索对象 . 找到下一个 __init__ 被调用 .

    • 依旧等等 .

    An explanation of super
    http://www.python.org/download/releases/2.2.3/descrintro/#cooperation
    Things to watch for when using super
    http://fuhm.net/super-harmful/
    Pythons MRO Algorithm:
    http://www.python.org/download/releases/2.3/mro/
    super's docs:
    http://docs.python.org/library/functions.html
    The bottom of this page has a nice section on super:
    http://docstore.mik.ua/orelly/other/python/0596001886_pythonian-chp-5-sect-2.html

    我希望这有助于澄清它 .

  • 6

    将你的代码更改为此,我认为它会解释事情(大概是 super 正在查看 B 在_1372982中的位置?):

    class A(object):
        def __init__(self):
            print "A init"
            print self.__class__.__mro__
    
    class B(A):
        def __init__(self):
            print "B init"
            print self.__class__.__mro__
            super(B, self).__init__()
    
    class C(A):
        def __init__(self):
            print "C init"
            print self.__class__.__mro__
            super(C, self).__init__()
    
    class D(B, C):
        def __init__(self):
            print "D init"
            print self.__class__.__mro__
            super(D, self).__init__()
    
    x = D()
    

    如果你运行它,你会看到:

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

    还值得一试Python's Super is nifty, but you can't use it .

  • 3

    只是猜测:

    所有四种方法中的 self 引用同一个对象,即类 D . 所以,在 B.__init__() 中,对 super(B,self) 的调用知道 self 的整个钻石祖先,它必须从'after' B 获取该方法 . 在这种情况下,它是 C 类 .

  • 34

    super() 知道 full 类层次结构 . 这是B的init中发生的事情:

    >>> super(B, self)
    <super: <class 'B'>, <D object>>
    

    这解决了核心问题,

    在D的init定义中super返回的对象如何将C的存在传达给B的init定义中super返回的对象?

    即,在B的init定义中, selfD 的实例,因此传达了 C 的存在 . 例如 C 可以在 type(self).__mro__ 中找到 .

  • 2

    Jacob的答案显示了如何理解这个问题,而batbrat则显示了细节,而hrr则直截了当 .

    从这个问题来看,他们没有涵盖(至少没有明确)的一点是:

    但是,如果你在B的init函数中注释掉对super的调用,则不会调用A和C的init函数 .

    要理解这一点,请将Jacob的代码更改为在A的init上打印堆栈,如下面:

    import traceback
    
    class A(object):
        def __init__(self):
            print "A init"
            print self.__class__.__mro__
            traceback.print_stack()
    
    class B(A):
        def __init__(self):
            print "B init"
            print self.__class__.__mro__
            super(B, self).__init__()
    
    class C(A):
        def __init__(self):
            print "C init"
            print self.__class__.__mro__
            super(C, self).__init__()
    
    class D(B, C):
        def __init__(self):
            print "D init"
            print self.__class__.__mro__
            super(D, self).__init__()
    
    x = D()
    

    看到 B 的行 super(B, self).__init__() 实际上正在调用 C.__init__() ,因为 C 不是 B 的基类,这有点令人惊讶 .

    D init
    (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
    B init
    (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
    C init
    (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
    A init
    (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
      File "/tmp/jacobs.py", line 31, in <module>
        x = D()
      File "/tmp/jacobs.py", line 29, in __init__
        super(D, self).__init__()
      File "/tmp/jacobs.py", line 17, in __init__
        super(B, self).__init__()
      File "/tmp/jacobs.py", line 23, in __init__
        super(C, self).__init__()
      File "/tmp/jacobs.py", line 11, in __init__
        traceback.print_stack()
    

    发生这种情况是因为 super (B, self) 没有'调用 __init__ '. Instead, it is '的B的基类版本'. Instead, it is ',在 BB 右边的第一个类上调用 __init__ 并且具有这样的属性 .

    所以,如果你在B的init函数中注释掉对super的调用,那么方法堆栈将在 B.__init__ 上停止,并且永远不会达到 CA .

    总结一下:

    • 无论哪个类引用它, self 始终是对实例的引用,其 __mro____class__ 保持不变

    • super()查找查找 __mro__ 上当前类右侧的类的方法 . 由于 __mro__ 保持不变,所以会发生的事情是将其作为列表进行搜索,而不是作为树或图形进行搜索 .

    在最后一点,请注意MRO算法的全名是C3超类线性化 . 也就是说,它将该结构展平为一个列表 . 当发生不同的 super() 调用时,它们会有效地迭代该列表 .

相关问题