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

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

其他回答

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

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

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

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

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

Python 2.3方法解析顺序

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

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

有3种不同的场景:

The base classes are unrelated, standalone classes. If your base classes are separate entities that are capable of functioning independently and they don't know each other, they're not designed for multiple inheritance. Example: class Foo: def __init__(self): self.foo = 'foo' class Bar: def __init__(self, bar): self.bar = bar Important: Notice that neither Foo nor Bar calls super().__init__()! This is why your code didn't work correctly. Because of the way diamond inheritance works in python, classes whose base class is object should not call super().__init__(). As you've noticed, doing so would break multiple inheritance because you end up calling another class's __init__ rather than object.__init__(). (Disclaimer: Avoiding super().__init__() in object-subclasses is my personal recommendation and by no means an agreed-upon consensus in the python community. Some people prefer to use super in every class, arguing that you can always write an adapter if the class doesn't behave as you expect.) This also means that you should never write a class that inherits from object and doesn't have an __init__ method. Not defining a __init__ method at all has the same effect as calling super().__init__(). If your class inherits directly from object, make sure to add an empty constructor like so: class Base(object): def __init__(self): pass Anyway, in this situation, you will have to call each parent constructor manually. There are two ways to do this: Without 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. 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. The important details here are: The mixin calls super().__init__() and passes through any arguments it receives. The subclass inherits from the mixin first: class FooBar(FooMixin, Bar). If the order of the base classes is wrong, the mixin's constructor will never be called. All base classes are designed for cooperative inheritance. Classes designed for cooperative inheritance are a lot like mixins: They pass through all unused arguments to the next class. Like before, we just have to call super().__init__() and all parent constructors will be chain-called. Example: 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 In this case, the order of the parent classes doesn't matter. We might as well inherit from CoopBar first, and the code would still work the same. But that's only true because all arguments are passed as keyword arguments. Using positional arguments would make it easy to get the order of the arguments wrong, so it's customary for cooperative classes to accept only keyword arguments. This is also an exception to the rule I mentioned earlier: Both CoopFoo and CoopBar inherit from object, but they still call super().__init__(). If they didn't, there would be no cooperative inheritance.

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

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

如果您可以控制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的(如果有的话)。