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

时间:2009-03-03 16:45:37

标签: python constructor multiple-inheritance super python-2.5

我正在运行Python 2.5,因此这个问题可能不适用于Python 3.当您使用多重继承创建钻石类层次结构并创建派生最多类的对象时,Python会执行Right Thing(TM)。它调用派生最多类的构造函数,然后调用从左到右列出的父类,然后是祖父母。我熟悉Python的MRO;那不是我的问题。我很好奇从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返回的对象?后续超级使用调用的信息是否存储在对象本身上?如果是这样,为什么不是super而不是self.super?

编辑:Jekke非常正确地指出它不是self.super,因为super是类的属性,而不是类的实例。从概念上讲,这是有道理的,但在实践中,超级也不是班级的属性!您可以在解释器中通过创建两个类A和B来测试它,其中B继承自A,并调用dir(B)。它没有super__super__属性。

5 个答案:

答案 0 :(得分:34)

将您的代码更改为此,我认为它会解释一些事情(大概super正在查看B中的__mro__在哪里?)

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

答案 1 :(得分:15)

我在下方提供了一系列链接,可以更详细地回答您的问题,而且比我希望的更准确。不过,我会用自己的话回答你的问题,为你节省一些时间。我会把它分成几点 -

  1. super是内置函数,而不是属性。
  2. Python中的每个类型(类)都有一个__mro__属性,用于存储该特定实例的方法解析顺序。
  3. 每次调用super都是super(type [,object-or-type])。我们假设第二个属性是目前的对象。
  4. 在超级调用的起点,该对象属于Derived类的类型(比如DC )。
  5. super在指定为第一个参数的类(在本例中为DC之后的类)之后查找MRO中的类中匹配的方法(在您的情况下为__init__)。
  6. 当找到匹配方法时(例如在课程 BC1 中),会调用它 (这个方法应该使用super,所以我假设它 - 看看Python的超级很漂亮但不能使用 - 链接如下) 然后,该方法会在对象的类“MRO”中搜索 BC1 右侧的下一个方法。
  7. 冲洗洗涤重复,直到找到并呼叫所有方法。
  8. 您的示例说明

     MRO: D,B,C,A,object  
    
    1. super(D, self).__init__()被调用。 isinstance(self,D)=&gt;真的
    2. 在D右侧的课程中搜索MRO中的下一个方法

      B.__init__找到并致电


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

        isinstance(self,B)=&gt;假
        isinstance(self,D)=&gt;真

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

      3. 依此类推。

      4. 超级的解释 http://www.python.org/download/releases/2.2.3/descrintro/#cooperation
        使用超级时要注意的事项
        http://fuhm.net/super-harmful/
        Pythons MRO算法:
        http://www.python.org/download/releases/2.3/mro/
        超级博士:
        http://docs.python.org/library/functions.html
        本页底部有一个很好的超级部分:
        http://docstore.mik.ua/orelly/other/python/0596001886_pythonian-chp-5-sect-2.html

        我希望这有助于澄清它。

答案 2 :(得分:6)

猜猜:

所有四种方法中的

self都引用同一个对象,即类D。 因此,在B.__init__()中,对super(B,self)的调用知道self的整个钻石祖先,并且必须从'{'B之后获取方法。在这种情况下,它是C类。

答案 3 :(得分:3)

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

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

这解决了核心问题,

  

super在D的init定义中返回的对象如何将C的存在与B的init定义中的super返回的对象进行通信?

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

答案 4 :(得分:2)

雅各布的答案显示了如何理解这个问题,而巴特布拉特则展示了细节,而且人们直截了当。

他们在你的问题中没有涵盖(至少没有明确)的一点是这一点:

  

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

要理解这一点,请将Jacob的代码更改为在A&lt; s init上打印堆栈,如下所示:

lbl_btn

有点令人惊讶的是,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__()不是{{1}的基类}}

C

这是因为B没有&#39; 调用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() &#39;的基类版本。相反,它是在super (B, self) __init__上出现的__init__右侧的第一个类上调用B并且具有这样的属性。

因此,如果您在B&lt; s&gt;的初始化函数中注释掉对super的调用,方法堆栈将停在self上,永远不会到达__mro__B.__init__

总结:

  • 无论哪个类引用它,C始终是对实例的引用,其Aself保持不变
  • super()查找方法查找__mro__上当前类右侧的类。由于__class__保持不变,所以会将其作为列表进行搜索,而不是作为树或图形进行搜索。

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