Calling parent class __init__ with multiple inheritance, what's the right way?
假设我有一个多重继承场景:
1 2 3 4 5 6 7 8 9 10 | 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? |
编写
但是,在这两种情况下,如果父类(
那么,正确的方法又是什么呢?很容易说"只是保持一致,跟着一个或另一个",但是如果
编辑:要了解我的意思,如果我这样做:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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") |
然后我得到:
1 2 3 4 5 6 7 8 | Entering C Entering A Entering B Leaving B Leaving A Entering B Leaving B Leaving C |
注意,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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") |
然后我得到:
1 2 3 4 | Entering C Entering A Leaving A Leaving C |
注意,不会调用
两种方法都可以。使用
在直接呼叫方式下,
使用
[稍后编辑的回答问题]
So it seems that unless I know/control the init's of the classes I
inherit from (A and B) I cannot make a safe choice for the class I'm
writing (C).
参考文章展示了如何通过在
有人可能希望多继承更容易,让您轻松地组合汽车和飞机类以获得一辆FlyingCar,但现实是,单独设计的组件在无缝装配之前通常需要适配器或包装器,正如我们希望的那样:—)
另一种想法是:如果您对使用多重继承组合功能不满意,可以使用组合来完全控制在哪些情况下调用哪些方法。
您的问题的答案取决于一个非常重要的方面:您的基类是为多继承设计的吗?
有三种不同的情况:
如果您的基类是单独的实体,它们能够独立运行,并且彼此不认识,那么它们不是为多重继承而设计的。例子:
1 2 3 4 5 6 7 | class Foo: def __init__(self): self.foo = 'foo' class Bar: def __init__(self, bar): self.bar = bar |
重要提示:注意,
这也意味着您不应该编写继承自
1 2 3 | class Base(object): def __init__(self): pass |
无论如何,在这种情况下,您必须手动调用每个父构造函数。有两种方法可以做到这一点:
无
super 。1
2
3
4class FooBar(Foo, Bar):
def __init__(self, bar='bar'):
Foo.__init__(self) # explicit calls without super
Bar.__init__(self, bar)与
super 一起1
2
3
4
5class 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
这两种方法各有优缺点。如果您使用
mixin是设计用于多继承的类。这意味着我们不必手动调用两个父构造函数,因为mixin将自动为我们调用第二个构造函数。因为我们这次只需要调用一个构造函数,所以我们可以使用
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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非常相似:它们将所有未使用的参数传递给下一个类。和以前一样,我们只需要调用
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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 |
在这种情况下,父类的顺序并不重要。我们还不如先从
这也是我前面提到的规则的一个例外:
底线:正确的实现取决于您继承的类。
构造函数是类的公共接口的一部分。如果该类被设计为混合继承或合作继承,那么必须对其进行记录。如果文档没有提到这类内容,那么可以安全地假定该类不是为合作的多重继承而设计的。
本文有助于解释合作多重继承:
http://www.artima.com/weblogs/viewpost.jsp?线程=281127
它提到了显示方法分辨率顺序的有用方法
下面是来自官方python站点的技术性文章:
http://www.python.org/download/releases/2.3/mro/
如果您从第三方库中复制子类,那么不,没有盲目的方法来调用实际工作的基类
从本质上讲,一个类是被设计成使用
正如Raymond在回答中所说,直接调用
但是,它不使用
1 2 3 4 5 6 | 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") |
如果您控制了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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") |
1 2 3 4 5 6 7 | >>> 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链。
您可以说这个案例是为多重继承而设计的。
源代码可访问:正确使用"旧样式"1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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") |
1 2 3 4 5 6 7 | >>> C() -> C -> A <- A -> B <- B <- C |
这里,MRO并不重要,因为
尽管这种情况并不像前一种样式那样"设计"用于新样式中的多个继承,但仍然可以进行多个继承。
现在,如果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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 |
1 2 3 4 5 6 7 | >>> C() -> C -> A <- A -> B <- B <- C |
类
如果是另一条路呢?
第三方父母:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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 |
1 2 3 4 5 6 7 | >>> C() -> C -> A <- A -> B <- B <- C |
这里的模式相同,只是执行顺序在
So it seems that unless I know/control the init's of the classes I inherit from (
A andB ) I cannot make a safe choice for the class I'm writing (C ).
虽然您可以使用适配器类来处理不控制