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

我正在运行Python 2.5,因此这个问题可能不适用于Python 3.当您使用多重inheritance创build钻石类层次结构并创build派生类的对象时,Python将执行Right Thing(TM)。 它调用派生类的构造函数,然后从左到右列出父类,然后是祖父类。 我熟悉Python的MRO ; 那不是我的问题。 我很好奇从超级对象返回的对象实际上是如何pipe理的,以正确的顺序在父类中调用超级调用。 考虑这个例子代码:

#!/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的初始化函数中注释掉超级调用,那么A和C的init函数都不会被调用。 这意味着B对super的调用在某种程度上意识到C在整个类层次结构中的存在。 我知道super用一个重载的get操作符返回一个代理对象,但是在D的初始化定义中super由super返回的对象如何将B的初始化定义中的C的存在传递给super所返回的对象呢? 是超级用户的后续调用信息存储在对象本身? 如果是这样,为什么不是超级而是self.super?

编辑:Jekke正确地指出,它不是self.super,因为super是类的一个属性,而不是类的一个实例。 从概念上讲,这是有道理的,但实际上超级也不是class级的属性! 你可以在解释器中做两个类A和B,其中Binheritance自A,然后调用dir(B) 。 它没有super__super__属性。

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

  1. 超级是一个内build函数,而不是一个属性。
  2. Python中的每个 (类)都有一个__mro__属性,用于存储特定实例的方法parsing顺序。
  3. 每个超级调用的forms超级(types[,对象或types])。 让我们假设第二个属性是目前的一个对象。
  4. 在超级调用的起始点,对象是Derived类( 比如DC )的types。
  5. super在MRO的类中查找匹配(在你的情况下是__init__ )的方法,在指定为第一个参数的类(在这种情况下是DC之后的类)之后。
  6. 当find匹配的方法(比如BC1类)时,它被调用。
    (这个方法应该使用超级,所以我假设它 – 看看Python的超级漂亮,但不能使用 – 下面的链接)该方法然后导致search对象的类MRO的下一个方法,右BC1
  7. 冲洗重复,直到所有的方法被发现和调用。

解释你的例子

  MRO: D,B,C,A,object 
  1. super(D, self).__init__()被调用。 isinstance(self,D)=> True
  2. 在D右侧的课程中searchMRO中的下一个方法

    B.__init__find并调用

  3. B.__init__调用super(B, self).__init__()

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

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

  5. 等等等等。

超级的解释
http://www.python.org/download/releases/2.2.3/descrintro/#cooperation
使用超级时要注意的事情
http://fuhm.net/super-harmful/
pythonMROalgorithm:
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

我希望这有助于清除它。

改变你的代码到这个,我认为它会解释的东西(大概是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的超级漂亮,但是你不能使用它 。

只是猜测:

所有四种方法中的self指的是同一个对象,也就是D类。 所以,在B.__init__() ,对super(B,self)的调用知道super(B,self)的全部菱形祖先,并且必须从'after'B中获取方法。 在这种情况下,它是C类。

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

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

这解决了中心问题,

在D的初始化定义中super由super返回的对象是如何将B的初始化定义中C的存在传递给super所返回的对象的?

也就是说,在B的init定义中, selfD一个实例,因此传达了C的存在。 例如C可以在type(self).__mro__

雅各布的回答显示了如何理解这个问题,而蝙蝠仔细的显示细节,人力资源部门直指点。

有一件事他们没有涉及(至less不是明确的)你的问题是这一点:

但是,如果在B的初始化函数中注释掉超级调用,那么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() 

看到Bsuper(B, self).__init__()线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的右边第一个类的__mro__ ,它具有这样一个属性

所以,如果你在B的init函数中注释掉超级调用 ,那么方法栈将停在B.__init__ ,并且永远不会到达C或者A

总结:

  • 无论哪个类引用它, self总是对实例的引用,其__mro____class__保持不变
  • super()find__mro__上的当前类的右边的__mro__ 。 由于__mro__保持不变,所发生的是它被作为一个列表search,而不是一棵树或一个graphics。

在最后一点上,请注意,MROalgorithm的全名是C3超类线性化 。 也就是说,它把这个结构变成了一个列表。 当不同的super()调用发生时,它们将有效地迭代该列表。