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

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)做出安全的选择。


当前回答

下面是我如何在Python 3中使用super()实现多重继承

class A:
  def __init__(self, a, b, **kwargs):
      print("Class A initiallised")
      self.a = a
      self.b = b
      super().__init__(**kwargs)
      print("Class A initiallisation done")

  def __str__(self):
      return f"{self.a} and {self.b}"

  def display_a(self):
      return f"{self.a} and {self.b}"

class C:
   def __init__(self, c, d, **kwargs):
      print("Class C initiallised")
      self.c = c
      self.d = d
      super().__init__(**kwargs)
      print("class c initiallisation done")

   def __str__(self):
      return f"{self.c} and {self.d}"

   def display_c(self):
       return f"{self.c} and {self.d}"


class D(A,C):
   def __init__(self, e, **kwargs):
       print("Class D initiallised")
       super().__init__(**kwargs)
       self.e = e
       print("Class D initiallisation done")

   def __str__(self):
      return f"{self.e} is e,{self.b} is b,{self.a} is a,{self.d} is d,{self.c} is c"

if __name__ == "__main__":
   d = D(a=12, b=13, c=14, d=15, e=16)
   print(d)
   d.display_c()
   d.display_a()

其他回答

正如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")

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

如果您可以控制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方法开始,任何使用super()方法的类都将跳转到相应的链位置,而任何不使用super()方法的类也将跳转到相应的链位置。

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

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

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

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

Python 2.3方法解析顺序