假设我有一个多重继承的场景:

class A(object):
    # code for A here

class B(object):
    # code for B here

class C(A, B):
    def __init__(self):
        # What's the right code to write here to ensure 
        # A.__init__ and B.__init__ get called?

有两种典型的方法来编写C语言的__init__:

(老式)ParentClass.__init__(自我) (new -style) super(DerivedClass, self).__init__()

然而,在任何一种情况下,如果父类(A和B)不遵循相同的约定,那么代码将不能正确工作(有些可能会丢失,或被多次调用)。

正确的方法是什么来着?说“保持一致,遵循其中一个”很容易,但如果A或B来自第三方库,那怎么办?是否有一种方法可以确保所有父类构造函数都被调用(并且以正确的顺序,并且只调用一次)?

编辑:看看我的意思,如果我这样做:

class A(object):
    def __init__(self):
        print("Entering A")
        super(A, self).__init__()
        print("Leaving A")

class B(object):
    def __init__(self):
        print("Entering B")
        super(B, self).__init__()
        print("Leaving B")

class C(A, B):
    def __init__(self):
        print("Entering C")
        A.__init__(self)
        B.__init__(self)
        print("Leaving C")

然后我得到:

Entering C
Entering A
Entering B
Leaving B
Leaving A
Entering B
Leaving B
Leaving C

注意B的init被调用了两次。如果我这样做:

class A(object):
    def __init__(self):
        print("Entering A")
        print("Leaving A")

class B(object):
    def __init__(self):
        print("Entering B")
        super(B, self).__init__()
        print("Leaving B")

class C(A, B):
    def __init__(self):
        print("Entering C")
        super(C, self).__init__()
        print("Leaving C")

然后我得到:

Entering C
Entering A
Leaving A
Leaving C

注意B的init从未被调用。因此,似乎除非我知道/控制从(A和B)继承的类的初始化,否则我无法为我正在编写的类(C)做出安全的选择。


当前回答

正如Raymond在他的回答中所说,直接调用a .__init__和B.__init__工作正常,您的代码将是可读的。

但是,它不使用C和这些类之间的继承链接。利用该链接可以提供更多的一致性,并使最终的重构更容易,更不容易出错。如何做到这一点的例子:

class C(A, B):
    def __init__(self):
        print("entering c")
        for base_class in C.__bases__:  # (A, B)
             base_class.__init__(self)
        print("leaving c")

其他回答

我添加了一个小型实用程序库superers,它使这种场景更容易处理。其工作原理如下:

class A(object):
    def __init__(self):
        print("Entering A")
        print("Leaving A")

class B(object):
    def __init__(self):
        print("Entering B")
        super(B, self).__init__()
        print("Leaving B")

class C(A, B):
    def __init__(self):
        print("Entering C")
        supers(self).__init__()
        print("Leaving C")

创建C时输出:

Entering C
Entering A
Leaving A
Entering B
Leaving B
Leaving C

本文有助于解释合作多重继承:

合作继承的奇迹,或者在python3中使用super

它提到了有用的方法mro(),该方法向您显示了方法解析顺序。在第二个示例中,在A中调用super,在MRO中继续执行super调用。顺序下一个类是B,这就是为什么B的init第一次被调用。

以下是来自Python官方网站的一篇更具技术性的文章:

Python 2.3方法解析顺序

这两种方法都很有效。使用super()的方法为子类带来了更大的灵活性。

在直接调用方法中,C.__init__可以同时调用A.__init__和B.__init__。

当使用super()时,类需要设计为合作多重继承,其中C调用super,它调用A的代码,后者也将调用super,后者调用B的代码。有关使用super可以做什么,请参阅http://rhettinger.wordpress.com/2011/05/26/super-considered-super。

[稍后编辑的回答问题]

所以除非我知道/控制类的初始化,否则我 我不能对我所在的班级做出安全的选择 写作(C)。

参考的文章展示了如何通过在a和b周围添加包装器类来处理这种情况。在“如何合并非合作类”一节中有一个设计好的示例。

有人可能希望多重继承更简单,让你毫不费力地组合Car和Airplane类来获得FlyingCar,但现实是,单独设计的组件通常需要适配器或包装器,然后才能像我们希望的那样无缝地组合在一起:

另一个想法是:如果你对使用多重继承的组合功能不满意,你可以使用组合来完全控制在什么情况下调用哪些方法。

如果您可以控制A和b的源代码,那么任何一种方法(“新样式”或“旧样式”)都可以工作。否则,可能需要使用适配器类。

源代码可访问:正确使用“new style”

class A(object):
    def __init__(self):
        print("-> A")
        super(A, self).__init__()
        print("<- A")

class B(object):
    def __init__(self):
        print("-> B")
        super(B, self).__init__()
        print("<- B")

class C(A, B):
    def __init__(self):
        print("-> C")
        # Use super here, instead of explicit calls to __init__
        super(C, self).__init__()
        print("<- C")
>>> C()
-> C
-> A
-> B
<- B
<- A
<- C

这里,方法解析顺序(MRO)规定如下:

C(A, B)先指示A,然后B。MRO是C -> A -> B ->对象。 super(A, self).__init__()沿着在C.__init__到B.__init__中初始化的MRO链继续。 super(B, self).__init__()沿着在C.__init__中初始化的MRO链继续到object.__init__。

可以说,这个案例是为多重继承而设计的。

源代码可访问:正确使用“旧样式”

class A(object):
    def __init__(self):
        print("-> A")
        print("<- A")

class B(object):
    def __init__(self):
        print("-> B")
        # Don't use super here.
        print("<- B")

class C(A, B):
    def __init__(self):
        print("-> C")
        A.__init__(self)
        B.__init__(self)
        print("<- C")
>>> C()
-> C
-> A
<- A
-> B
<- B
<- C

这里,MRO无关紧要,因为显式地调用了A.__init__和B.__init__。C类(B, A):同样有效。

尽管这种情况不像前一种情况那样是为新样式中的多重继承而“设计”的,但多重继承仍然是可能的。


现在,如果A和B来自第三方库怎么办——也就是说,您无法控制A和B的源代码?简单的回答是:您必须设计一个适配器类来实现必要的super调用,然后使用一个空类来定义MRO(参见Raymond Hettinger关于super的文章——特别是“如何合并非协作类”一节)。

第三方父级:A不实现超级;B确实

class A(object):
    def __init__(self):
        print("-> A")
        print("<- A")

class B(object):
    def __init__(self):
        print("-> B")
        super(B, self).__init__()
        print("<- B")

class Adapter(object):
    def __init__(self):
        print("-> C")
        A.__init__(self)
        super(Adapter, self).__init__()
        print("<- C")

class C(Adapter, B):
    pass
>>> C()
-> C
-> A
<- A
-> B
<- B
<- C

类Adapter实现了super,这样C就可以定义MRO,当super(Adapter, self).__init__()被执行时,MRO就开始发挥作用了。

如果反过来呢?

第三方父类:A实现super;B没有

class A(object):
    def __init__(self):
        print("-> A")
        super(A, self).__init__()
        print("<- A")

class B(object):
    def __init__(self):
        print("-> B")
        print("<- B")

class Adapter(object):
    def __init__(self):
        print("-> C")
        super(Adapter, self).__init__()
        B.__init__(self)
        print("<- C")

class C(Adapter, A):
    pass
>>> C()
-> C
-> A
<- A
-> B
<- B
<- C

这里的模式相同,只是在Adapter.__init__中执行顺序发生了切换;首先是超级调用,然后是显式调用。注意,每个带有第三方父类的情况都需要一个唯一的适配器类。

因此,似乎除非我知道/控制从(A和B)继承的类的初始化,否则我无法为我正在编写的类(C)做出安全的选择。

虽然您可以通过使用适配器类来处理无法控制A和B的源代码的情况,但确实必须知道父类的init是如何实现super的(如果有的话)。

它遵循MRO规则,并调用init。