问题:用多重继承调用父类__init__,正确的方法是什么?
假设我有多个继承方案:
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__(self)
- (较新的样式) 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
请注意,Binit会被调用两次。如果我做:
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)做出安全选择。
回答 0
两种方式都可以正常工作。使用该方法super()可为子类带来更大的灵活性。  
在直接呼叫方式中,C.__init__可以同时呼叫A.__init__和B.__init__。
使用时super(),需要将类设计为在其中C调用的协作式多重继承super,这将调用A的代码,该代码还将super调用B的代码。请参阅http://rhettinger.wordpress.com/2011/05/26/super-considered-super,以详细了解可以使用进行的操作super。
[回答问题,稍后编辑]
因此,似乎除非我知道/控制我从(A和B)继承的类的初始化,否则我无法对我正在编写的类(C)做出安全的选择。
参考的文章显示了如何通过在A和周围添加包装器类来处理这种情况B。标题为“如何合并非合作类”的部分提供了一个可行的示例。
可能希望多重继承更容易,让您轻松组成Car和Airplane类来获得FlyingCar,但现实情况是,单独设计的组件通常需要适配器或包装器,然后才能像我们希望的那样无缝地组装在一起:-)
另一个想法:如果您对使用多重继承来编写功能不满意,则可以使用composition来完全控制在哪些情况下调用哪种方法。
回答 1
您问题的答案取决于一个非常重要的方面:您的基类是否设计用于多重继承?
有3种不同的方案:
- 基类是不相关的独立类。- 如果您的基类是能够独立运行的独立实体,并且彼此之间不认识,则它们不是为多重继承而设计的。例: - class Foo: def __init__(self): self.foo = 'foo' class Bar: def __init__(self, bar): self.bar = bar- 重要:请注意,既不打电话 - Foo也不- Bar打电话- super().__init__()!这就是为什么您的代码无法正常工作的原因。由于Diamond继承在python中的工作方式,因此- object不应调用基类为的类- super().__init__()。如您所知,这样做会破坏多重继承,因为您最终将调用另一个类的- __init__而不是- object.__init__()。(免责声明:避免- super().__init__()在- object-subclasses中是我个人的建议,绝不是python社区中达成一致的共识。有些人更喜欢- super在每个类中使用,认为如果该类的行为不像您通常可以编写一个适配器您期望的。)- 这也意味着您永远不应编写从其继承 - object且没有- __init__方法的类。完全不定义- __init__方法与调用具有相同的效果- super().__init__()。如果您的类直接继承自- object,请确保添加一个空的构造函数,如下所示:- class Base(object): def __init__(self): pass- 无论如何,在这种情况下,您将必须手动调用每个父构造函数。有两种方法可以做到这一点: - 不带 - super- class FooBar(Foo, Bar): def __init__(self, bar='bar'): Foo.__init__(self) # explicit calls without super Bar.__init__(self, bar)
- 用 - super- class FooBar(Foo, Bar): def __init__(self, bar='bar'): super().__init__() # this calls all constructors up to Foo super(Foo, self).__init__(bar) # this calls all constructors after Foo up # to Bar
 - 这两种方法各有其优点和缺点。如果你使用 - super,你的类将支持依赖注入。另一方面,容易出错。例如,如果你改变的顺序- Foo和- Bar(像- class FooBar(Bar, Foo)),你就必须更新- super到匹配的电话。没有- super您,不必担心这一点,并且代码更具可读性。
- 类之一是mixin。- 一混入是,这是一个一流的设计与多重继承使用。这意味着我们不必手动调用两个父构造函数,因为mixin会自动为我们调用第二个构造函数。由于这次只需要调用一个构造函数,因此 - super可以避免对父类的名称进行硬编码。- 例: - class FooMixin: def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # forwards all unused arguments self.foo = 'foo' class Bar: def __init__(self, bar): self.bar = bar class FooBar(FooMixin, Bar): def __init__(self, bar='bar'): super().__init__(bar) # a single call is enough to invoke # all parent constructors # NOTE: `FooMixin.__init__(self, bar)` would also work, but isn't # recommended because we don't want to hard-code the parent class.- 这里的重要细节是: - mixin调用super().__init__()并通过它接收的任何参数。
- 子类首先从mixin继承:class FooBar(FooMixin, Bar)。如果基类的顺序错误,则将永远不会调用mixin的构造函数。
 
- mixin调用
- 所有基类均设计用于协作继承。- 专为合作继承而设计的类非常类似于mixin:它们将所有未使用的参数传递给下一类。和以前一样,我们只需要调用即可 - super().__init__(),所有父级构造函数都将被链调用。- 例: - class CoopFoo: def __init__(self, **kwargs): super().__init__(**kwargs) # forwards all unused arguments self.foo = 'foo' class CoopBar: def __init__(self, bar, **kwargs): super().__init__(**kwargs) # forwards all unused arguments self.bar = bar class CoopFooBar(CoopFoo, CoopBar): def __init__(self, bar='bar'): super().__init__(bar=bar) # pass all arguments on as keyword # arguments to avoid problems with # positional arguments and the order # of the parent classes- 在这种情况下,父类的顺序无关紧要。我们 - CoopBar最好还是从头继承,而代码仍然可以正常工作。但这是真的,因为所有参数都作为关键字参数传递。使用位置参数将很容易弄错参数的顺序,因此,协作类习惯于仅接受关键字参数。- 这也是我前面提到的规则的一个exceptions: - CoopFoo和- CoopBar都继承自- object,但它们仍然调用- super().__init__()。如果没有,则不会有合作继承。
底线:正确的实现取决于您从其继承的类。
构造函数是类的公共接口的一部分。如果该类被设计为混合或协作继承,则必须将其记录下来。如果文档中未提及任何内容,则可以安全地假定该类不是为协作多重继承而设计的。
回答 2
这两种方法(“新风格”或“旧式”),将工作,如果你有过的源代码控制A和B。否则,可能需要使用适配器类。
可访问的源代码:正确使用“新样式”
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 -> object。
- super(A, self).__init__()沿始于的MRO链继续- C.__init__进行- B.__init__。
- super(B, self).__init__()沿始于的MRO链继续- C.__init__进行- 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__被显式调用。class C(B, A):也会一样工作。
尽管这种情况不是像以前的样式那样“设计”为新样式的多重继承,但多重继承仍然是可能的。
现在,如果A和B是从第三方库-即你有过的源代码没有控制A和B?简短的答案:您必须设计一个实现必要super调用的适配器类,然后使用一个空类来定义MRO(请参阅Raymond Hettinger上的文章super -尤其是“如何合并非合作类”一节)。
第三方家长:A未实施super;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,该MRO在super(Adapter, self).__init__()执行时起作用。
如果反过来呢?
第三方父母: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__;super先呼叫,然后再进行显式呼叫。请注意,带有第三方父母的每种情况都需要一个唯一的适配器类。
因此,似乎除非我知道/控制我从(
A和B)继承的类的初始化,否则我无法对正在编写的类(C)做出安全选择。
虽然你可以处理,你没有的情况下,控制的源代码A,并B通过使用适配器类,这是事实,你必须知道在init怎样的父类实现super(如果有的话),以这样做。
回答 3
正如雷蒙德(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")
回答 4
本文有助于解释协作式多重继承:
http://www.artima.com/weblogs/viewpost.jsp?thread=281127
它提到了有用的方法mro(),可向您显示方法解析顺序。在你的第二个例子,当你调用super中A,该super呼叫继续在MRO。顺序中的下一个类是B,这就是为什么Binit首次被调用的原因。
这是来自python官方站点的更多技术文章:
回答 5
如果要从第三方库中繁衍子类类,则不会,没有盲目的方法来调用__init__实际上起作用的基类方法(或任何其他方法),而不管基类的编程方式如何。
super使编写旨在协作实现方法的类成为复杂的多重继承树的一部分成为可能,而类继承者不必知道。但是无法使用它正确地从可能使用或可能不使用的任意类中继承super。
本质上,一个类是设计为使用super基类还是直接调用基类来进行子类化,是属于该类“公共接口”一部分的属性,因此应进行记录。如果您以库作者所期望的方式使用第三方库,并且库具有合理的文档,则通常会告诉您需要做什么来对特定的事物进行子类化。如果不是,那么您必须查看要子类化的类的源代码,并查看其基类调用约定是什么。如果你是从一个或多个第三方库的方式,该库作者结合多个类没想到,那么它可能无法始终如一地调用超类的方法在所有; 如果类A是使用的层次结构的一部分,super而类B是不使用super的层次结构的一部分,则不能保证这两种选择都不会起作用。您将必须找出一种适用于每个特定案例的策略。

