使用多重继承调用父类__init__,这是正确的方法吗?

时间:2012-03-05 23:00:32

标签: python oop inheritance multiple-inheritance super

假设我有一个多重继承方案:

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的{​​{1}}时有两种典型方法:

  1. (旧式)__init__
  2. (较新式)ParentClass.__init__(self)
  3. 但是,在任何一种情况下,如果父类(super(DerivedClass, self).__init__()Adon't follow the same convention, then the code will not work correctly(有些可能会被错过,或被多次调用)。

    那么又是什么样的正确方法呢?很容易说“只是保持一致,遵循一个或另一个”,但如果BA来自第三方图书馆,那么呢?是否有一种方法可以确保所有父类构造函数被调用(并且以正确的顺序,并且只有一次)?

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

    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 的init被调用两次。如果我这样做:

    B

    然后我得到:

    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 的init永远不会被调用。所以看起来,除非我知道/控制我从(BA继承的类的init,否则我无法为我写的类(B)做出安全的选择。

10 个答案:

答案 0 :(得分:56)

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

在直接通话方式中,C.__init__可以同时调用A.__init__B.__init__

使用super()时,需要针对合作多重继承设计类,C调用super,调用A的代码,该代码也会调用super 1}}调用B的代码。有关super可以执行哪些操作的详细信息,请参阅http://rhettinger.wordpress.com/2011/05/26/super-considered-super

[后面编辑的回复问题]

  

所以似乎除非我知道/控制类的初始化我   继承自(A和B)我不能为我的班级做出安全的选择   写作(C)。

参考文章介绍了如何通过在AB周围添加包装类来处理这种情况。在标题为“如何融入非合作班级”一节中有一个经过深思熟虑的例子。

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

另一个想法是:如果您对使用多重继承编写功能感到不满意,可以使用合成来完全控制在哪些情况下调用哪些方法。

答案 1 :(得分:3)

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

http://www.artima.com/weblogs/viewpost.jsp?thread=281127

它提到了有用的方法mro(),它显示了方法解析顺序。在您的super中呼叫A的第二个示例中,super呼叫在MRO中继续。顺序中的下一个类是B,这就是第一次调用B初始化的原因。

以下是来自官方python网站的更多技术文章:

http://www.python.org/download/releases/2.3/mro/

答案 2 :(得分:2)

如果你是来自第三方库的多级子类,那么不,没有盲目的方法来调用实际工作的基类__init__方法(或任何其他方法),无论基类如何编程。

super使可能编写用于协作实现方法的类,作为复杂的多重继承树的一部分,这些树不需要为类作者所知。但是没有办法用它来正确地继承可能使用super的任意类。

基本上,一个类是使用super设计为子类还是直接调用基类是一个属性,它是类“公共接口”的一部分,它应该记录为这样。如果您以库作者期望的方式使用第三方库并且库具有合理的文档,则通常会告诉您要对特定事物进行子类化所需的操作。如果没有,那么你将不得不查看你正在进行子类化的类的源代码,看看他们的基类调用约定是什么。如果您以一种库作者没有期望的方式组合来自一个或多个第三方库的多个类,那么可能无法一致地调用超类方法在所有;如果类A是使用super的层次结构的一部分而类B是不使用超级的层次结构的一部分,那么这两个选项都不能保证工作。你必须找出适用于每个特定案例的策略。

答案 3 :(得分:2)

如果您可以控制AB 的源代码,则两种方法(“新样式”或“旧样式”)都将有效。否则,可能有必要使用适配器类。

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

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__()沿着在C.__init__中发起的MRO链一直延伸到B.__init__
  • 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__被显式调用。 class C(B, A):同样适用。

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


现在,如果AB来自第三方库怎么办?也就是说,您无法控制AB的源代码?简短的答案:您必须设计一个实现必要的super调用的适配器类,然后使用一个空的类来定义MRO(请参见Raymond Hettinger's article on super-尤其是“如何合并非合作社”部分类”。

第三方父母:A未实现superB可以

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实现superB

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首先调用,然后进行显式调用。请注意,每个带有第三方父母的案例都需要一个唯一的适配器类。

  

因此,似乎除非我知道/控制我从中继承的类的初始化(AB),否则我无法为正在编写的类({{1 }}。

尽管您可以通过使用适配器类来处理不控制CA的源代码的情况,但确实必须< em>知道父类的初始化如何实现B(如果有的话)。

答案 4 :(得分:1)

正如雷蒙德在回答中所说,直接调用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")

答案 5 :(得分:1)

1。首先,假设您拥有MRO链

2。从最低级别的子类 init 方法开始,任何使用super()方法的类都将跳到相应的链位置,因为任何不使用super()方法的类都将跳出相应的链位置链位置。

如果这里有错误,请纠正我!

答案 6 :(得分:1)

它遵循MRO规则,并且称为init

答案 7 :(得分:0)

我添加了一个小型实用程序库supers,它使这种情况更易于处理。其工作原理如下:

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

答案 8 :(得分:0)

这是我如何使用 super() 在 python 3 中实现多重继承

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()

答案 9 :(得分:0)

这里是我如何在python继承中实现super方法并实现所需的解决方案:

class A:
    def __init__(self):
        print("from A")

class B:
    def __init__(self):
        print("from B")
class C(A,B):
    def __init__(self):
          A.__init__(self)
          B.__init__(self)
          print ("from C")
c = C()