用多重继承调用父类__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__:

  1. (老式)ParentClass.__init__(self)
  2. (新型)super(DerivedClass, self).__init__()

然而,在这两种情况下,如果父类(AB) 如果不遵循相同的约定,那么代码将无法正常工作(有些可能会被错过,或被多次调用)。

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

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

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从未被调用。因此,似乎除非我知道/控制继承自(AB)的类的初始化,否则我无法为我正在编写的类(C)做出安全的选择。

151059 次浏览

这两种方法都很有效。使用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) . < / p >

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

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

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

如果你从第三方库中有多个子类化类,那么不,没有盲目的方法来调用基类__init__方法(或任何其他方法),不管基类是如何编程的,这些方法实际上都是有效的。

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

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

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

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

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

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

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

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

你的问题的答案取决于一个非常重要的方面:基类是为多重继承设计的吗?

有3种不同的场景:

  1. 基类是不相关的独立类。

    如果你的基类是独立的实体,能够独立工作,并且它们彼此不认识,它们是设计用于多重继承。例子:

    class Foo:
    def __init__(self):
    self.foo = 'foo'
    
    
    class Bar:
    def __init__(self, bar):
    self.bar = bar
    

    Bar0注意,FooBar都没有调用super().__init__()!这就是你的代码不能正常工作的原因。由于python中菱形继承的工作方式,Bar1。正如你所注意到的,这样做会破坏多重继承,因为你最终调用的是另一个类的__init__而不是object.__init__()Bar2

    这也意味着永远不要编写继承自object且没有__init__方法的类。完全不定义__init__方法与调用super().__init__()具有相同的效果。如果你的类直接继承自object,请确保添加一个空构造函数,如下所示:

    class Base(object):
    def __init__(self):
    pass
    

    无论如何,在这种情况下,您必须手动调用每个父构造函数。有两种方法:

    • < p > 没有super

      class FooBar(Foo, Bar):
      def __init__(self, bar='bar'):
      Foo.__init__(self)  # explicit calls without super
      Bar.__init__(self, bar)
      
    • With 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
      

    Each of these two methods has its own advantages and disadvantages. If you use super, your class will support dependency injection. On the other hand, it's easier to make mistakes. For example if you change the order of Foo and Bar (like class FooBar(Bar, Foo)), you'd have to update the super calls to match. Without super you don't have to worry about this, and the code is much more readable.

  2. One of the classes is a mixin.

    A mixin is a class that's designed to be used with multiple inheritance. This means we don't have to call both parent constructors manually, because the mixin will automatically call the 2nd constructor for us. Since we only have to call a single constructor this time, we can do so with super to avoid having to hard-code the parent class's name.

    Example:

    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的构造函数将永远不会被调用。
    • 李< / ul > < / >
    • 所有基类都是为合作继承而设计的。

      为合作继承设计的类很像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继承,代码仍然可以正常工作。但这只是因为所有参数都作为关键字参数传递。使用位置参数很容易使参数的顺序出错,因此合作类习惯上只接受关键字参数。

      这也是我之前提到的规则的一个例外:CoopFooCoopBar都继承自object,但它们仍然调用super().__init__()。如果他们不这样做,就没有合作继承

底线:正确的实现取决于继承的类。

构造函数是类的公共接口的一部分。如果类被设计为mixin或用于合作继承,则必须将其记录在案。如果文档中没有提及此类内容,则可以安全地假设类不是是为合作多重继承而设计的。

任何一种方法(“新样式”或“旧样式”)都可以工作如果你能控制__ABC0和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 -> object
  • super(A, self).__init__()沿着从C.__init__B.__init__开始的MRO链继续。
  • super(B, self).__init__()沿着从C.__init__object.__init__开始的MRO链继续。

你可以说这种情况是设计用于多重继承

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

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来自第三方库——即你无法控制__ABC0和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,当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__中切换了执行顺序;super先调用,然后显式调用。注意,每个带有第三方父类的情况都需要一个唯一的适配器类。

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

虽然你可以通过使用适配器类来处理不控制 AB的源代码的情况,但你必须知道父类的init如何实现super(如果有的话)才能做到这一点。

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

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

1.首先,假设您有MRO链

2.从最底层的子类初始化方法开始,任何使用super()方法的类都将跳转到相应的链位置,因为任何不使用super()方法的类都将跳出相应的链位置。

如果这里有错误,请指正!

它遵循MRO规则,并调用init

下面是我如何在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()

以下是我如何在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()