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

问题描述 投票:50回答: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返回的对象?后续超级使用调用的信息是否存储在对象本身上?如果是这样,为什么不是超级而不是self.super?

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

python constructor multiple-inheritance super python-2.5
5个回答
15
投票

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

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

您的示例的说明

 MRO: D,B,C,A,object  
  1. super(D, self).__init__()被称为。 isinstance(self,D)=>真
  2. 在D的右侧的类中搜索MRO中的下一个方法。 B.__init__找到并打电话给

  1. B.__init__super(B, self).__init__()。 isinstance(self,B)=> False isinstance(self,D)=>真
  2. 因此,MRO是相同的,但是搜索继续到B的右边,即C,A,逐个搜索对象。发现的下一个__init__被称为。
  3. 等等等等。

超级的解释 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

我希望这有助于澄清它。


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


6
投票

只是猜测:

所有四种方法中的self指的是同一个对象,即类D。因此,在B.__init__(),对super(B,self)的召唤知道self的整个钻石血统,它必须从'之后'B获取方法。在这种情况下,它是C类。


3
投票

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

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

这解决了核心问题,

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

也就是说,在B的初始定义中,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)并没有“调用B的基础版__init__”。相反,它是在__init__B上存在的self右边的第一类调用__mro__并且具有这样的属性。

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

总结一下:

  • 无论哪个类指的是它,self始终是对实例的引用,其__mro____class__保持不变
  • super()找到查找__mro__上当前类右边的类的方法。由于__mro__保持不变,所发生的是它被搜索为列表,而不是树或图形。

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

© www.soinside.com 2019 - 2024. All rights reserved.