【问题标题】:Python multiple inheritance and super()Python 多重继承和 super()
【发布时间】:2016-02-25 08:39:00
【问题描述】:

我试图了解多重继承(即C3 algorithm for method resolution order)在 Python 中的工作原理。下面带有经典diamond dependency 的玩具示例给出了与我的直觉相反的结果。

我特别注意到以下几点:

  1. 按原样运行时(即,AAA 都有 super 调用 Base,则输出为:<B> <A> <AA> <Base> </Base> </AA> </A> </B>
  2. 当仅注释掉标记为(1) 的行(即,在A 中没有调用Base 构造函数),则输出为<B> <A> </A> </B>
  3. 当仅注释掉标记为(2) 的行时(即,在AA 中没有调用Base 构造函数),则输出为<B> <A> <AA> </AA> </A> </B>
  4. 当标记为(1)(2) 的两行都被注释掉时,输出为<B> <A> </A> </B>

我的问题是:

  • 在情况 1 中,在显式调用 Base 构造函数之前,似乎在 A 的构造函数中执行被中断,跳转到(“递归到”)AA 构造函数(好像 AA将派生自A),然后下降到Base,然后退出。这是怎么回事? (我理解 MRO B->A->AA->Base 来自 C3 要求,即在父类之前调用​​子类。)
  • 在案例 2 中,为什么 AA 的构造函数从未被调用,而在案例 3 中它被调用了?
  • 在情况 2 和 3 中,尽管在 AA(情况 2)/A(情况 3)的构造函数中进行了显式调用,为什么没有调用 Base 构造函数? (在示例 1 中,它的调用方式符合我的预期。)

以下是课程的 MRO:

  • B:(<class '__main__.B'>, <class '__main__.A'>, <class '__main__.AA'>, <class '__main__.Base'>, <type 'object'>)
  • A:(<class '__main__.A'>, <class '__main__.Base'>, <type 'object'>)
  • AA:(<class '__main__.AA'>, <class '__main__.Base'>, <type 'object'>)
  • Base:(<class '__main__.Base'>, <type 'object'>)

代码:

#!/usr/bin/env python
# Using Python 2.7

class Base(object):
    def __init__(self):
        print '<Base>',
        super(Base, self).__init__()
        print '</Base>',

class A(Base):
    def __init__(self):
        print '<A>',
        super(A, self).__init__()             # (1)
        print '</A>',

class AA(Base):
    def __init__(self):
        print '<AA>',
        super(AA, self).__init__()            # (2)
        print '</AA>',

class B(A, AA):
    def __init__(self):
        print '<B>',
        super(B, self).__init__()
        print '</B>',

if __name__ == '__main__':
    obj = B()

【问题讨论】:

标签: python inheritance constructor multiple-inheritance


【解决方案1】:

MRO 是 BAAABase。这意味着当selfB 对象时super(A, self) 的值是AA 类的代理。如果您构造了一个A 对象,则相同的super 调用将返回Base 类的代理。

所有让你困惑的行为都直接源于此。

在 A 对象中,super(A,self).__init__() 调用将调用 Base.__init__(self),但 B 对象中的相同调用将调用 AA.__init__(self)

编辑以在您的评论中添加问题的答案:

不,你找不到 super 解析的类,原因很简单,不同的属性可以解析为不同的类。

例如,在您的代码中给class AA 一个方法foo。现在:

b = B()
super(B,b).__init__() # calls A.__init__
super(B,b).foo() # calls AA.foo

super 函数不仅查找 MRO 链中的下一个类,它还查找具有所需属性的下一个类。这就是为什么它必须返回一个代理对象,而不仅仅是返回一个类。

【讨论】:

  • 谢谢,现在说得通了。有没有办法让super 解析的类名称(用于进一步实验)?我试过super(A).__class__,但这并没有产生任何有用的东西。
  • @Powerfool,我更新了我的答案以解释为什么通常引用“超级解决的类”是没有意义的。
  • 谢谢邓肯,我没有考虑到这方面。动态语言的复杂性开销似乎相当大,例如 C++ 中的虚拟继承。此外,虽然 C3 现在很有意义,但我想知道普通程序员是否总是发现与虚拟继承等更静态的机制相比,这种行为立即直观。接下来我会讨论这种间接的性能后果,但在某些时候我可能应该提出一个新问题。
【解决方案2】:

您不应该将super 视为对继承链中下一个“up”的函数调用。相反,如果使用得当,super 将确保按该顺序调用 MRO 中的所有函数。但是为了达到顶峰,超级调用需要在该链的每个部分中。

因此,如果您删除 AAA 中的超级调用,则链会中断。根据您删除的内容,链会在AAA 处中断:

  • 不间断(完整 MRO):BAAABase
  • 案例1(在A中没有超级调用):BA
  • 案例2(在AA中没有超级调用); B, A, AA

因此,您应该记住始终在所有涉及的类型中始终使用 super 以使其正常运行。

如果您想了解更多关于 super 的信息,您应该在今年的 PyCon 上查看 Raymond Hettinger’s talk “Super considered super!”。它解释得很好,也有一些易于理解的例子(涉及真实的人!)。

从那次谈话中引用他的话(我的转录和强调):

super 在 Python 中最大的问题是什么?这不是它的设计。我认为它的设计完美无瑕,很漂亮,Guido 做得非常出色。

问题是名字,它不应该被称为“超级”。为什么不?答案是,如果你用任何其他语言学习 super,它的效果与 Python 不同。

[...] 它在其他语言中的作用是什么?在其他语言中,它会呼叫您的父母。 […] 继承,每当你打电话给super 时,就是打电话给你的父母。但是 Python 的做法不同。它确实调用了父母,但不是你的父母。当您拨打super 时,会呼叫谁的父母? 不是你的祖先,而是你孩子的祖先。

【讨论】:

    【解决方案3】:

    来自您链接的维基百科文章:

    Python 使用 C3 线性化算法创建一个类列表。该算法强制执行两个约束:子类在其父类之前,如果一个类从多个类继承,它们将按照基类元组中指定的顺序保存(但是在这种情况下,继承图中的某些类可能在较低的类之前图[8])。因此,方法解析顺序为:D、B、C、A

    所以在这种情况下(即在 B 的继承结构中),A 的父级是 AA。

    【讨论】:

    • 谢谢,我忘了图线性化的真正含义。在这种情况下,MRO 实际上是有意义的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-02-11
    • 2017-07-30
    • 2020-07-05
    • 2014-07-01
    • 2015-05-04
    • 1970-01-01
    • 2021-11-28
    相关资源
    最近更新 更多