假设我有一个多重继承场景:
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__(self)
super(DerivedClass, self).__init__()
但是,在这两种情况下,如果父类(A
和B
)don't follow the same convention, then the code will not work correctly(有些可能会丢失,或者被多次调用)
那么正确的方法又是什么呢?说“保持一致,遵循一个或另一个”很容易,但是如果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
)继承的类的init,否则我无法为我正在编写的类(C
)做出安全的选择
您的问题的答案取决于一个非常重要的方面:您的基类是为多重继承设计的吗?
有3种不同的场景:
基类是不相关的独立类
如果您的基类是能够独立运行的独立实体,并且它们彼此不认识,那么它们不是为多重继承而设计的。例如:
重要提示:请注意
Foo
和Bar
都不调用super().__init__()
!这就是你的代码不能正常工作的原因。由于菱形继承在python中的工作方式,基类为object
的类不应调用super().__init__()
。正如您所注意到的,这样做会破坏多重继承,因为您最终会调用另一个类的__init__
,而不是object.__init__()
(免责声明:避免object
中的super().__init__()
-子类是我个人的建议,绝不是python社区的共识。有些人更喜欢在每个类中使用super
,他们认为如果类的行为不符合您的预期,您总是可以编写adapter。)这也意味着您不应该编写从
object
继承而没有__init__
方法的类。根本不定义__init__
方法与调用super().__init__()
具有相同的效果。如果类直接从object
继承,请确保添加空构造函数,如下所示:无论如何,在这种情况下,您必须手动调用每个父构造函数。有两种方法可以做到这一点:
没有
super
与
super
这两种方法各有优缺点。如果您使用
super
,您的类将支持dependency injection。另一方面,更容易犯错误。例如,如果更改Foo
和Bar
的顺序(如class FooBar(Bar, Foo)
),则必须更新super
调用以匹配。如果没有super
,您就不必担心这一点,代码的可读性也会更好其中一个类是mixin
Amixin是一个设计用于多重继承的类。这意味着我们不必手动调用两个父构造函数,因为mixin将自动为我们调用第二个构造函数。因为这次我们只需要调用一个构造函数,所以我们可以使用
super
来避免硬编码父类的名称例如:
这里的重要细节是:
super().__init__()
并传递它接收到的任何参数李>class FooBar(FooMixin, Bar)
。如果基类的顺序错误,则永远不会调用mixin的构造函数李>所有基类都是为协作继承而设计的
为协作继承设计的类非常类似于mixin:它们将所有未使用的参数传递给下一个类。像前面一样,我们只需调用
super().__init__()
,所有父构造函数都将被链调用例如:
在这种情况下,父类的顺序并不重要。我们不妨先从
CoopBar
继承,代码仍然可以工作。但这是唯一正确的,因为所有参数都作为关键字参数传递。使用位置参数会使参数的顺序很容易出错,因此协作类通常只接受关键字参数这也是我前面提到的规则的一个例外:
CoopFoo
和CoopBar
都继承自object
,但它们仍然调用super().__init__()
。如果他们不这样做,就不会有合作继承一句话:正确的实现取决于您从中继承的类
构造函数为Par类的公共接口的t。如果类被设计为mixin或用于协作继承,则必须对此进行记录。如果文档没有提到这类内容,那么可以安全地假设类不是为协作多重继承而设计的
两种方法都很好。使用
super()
的方法为子类带来了更大的灵活性在直接调用方法中,
C.__init__
可以同时调用A.__init__
和B.__init__
当使用
super()
时,类需要设计为协作多重继承,其中C
调用super
,调用A
的代码,该代码也将调用调用B
的代码。有关如何使用super
的更多详细信息,请参见http://rhettinger.wordpress.com/2011/05/26/super-considered-super[回答问题,稍后编辑]
参考文章展示了如何通过在
A
和B
周围添加包装类来处理这种情况。在题为“如何合并非合作类”的章节中有一个已制定的示例有人可能希望多重继承更容易,让您可以毫不费力地编写Car和Planet类来获得FlyingCar,但现实情况是,单独设计的组件通常需要适配器或包装器,然后才能像我们希望的那样无缝地装配在一起:-)
另一个想法是:如果您对使用多重继承组合功能不满意,您可以使用组合来完全控制在哪些情况下调用哪些方法
如果您可以控制
A
和B
的源代码,则任何一种方法(“新样式”或“旧样式”)都将有效。否则,可能需要使用适配器类可访问的源代码:正确使用“新样式”
这里,方法解析顺序(MRO)规定了以下内容:
C(A, B)
先指示A
,然后B
。MRO是C -> A -> B -> object
李>super(A, self).__init__()
沿着在{super(B, self).__init__()
沿着在{您可以说这个案例是为多重继承而设计的
可访问的源代码:正确使用“旧样式”
在这里,MRO并不重要,因为
A.__init__
和B.__init__
被显式调用class C(B, A):
也同样有效虽然这个案例并不像前一个案例那样为新样式中的多重继承“设计”,但多重继承仍然是可能的
现在,如果} -特别是“如何合并非协作类”一节)
A
和B
来自第三方库,即,您无法控制A
和B
的源代码,该怎么办?简短的回答是:您必须设计一个适配器类来实现必要的super
调用,然后使用一个空类来定义MRO(请参见Raymond Hettinger's article on ^{第三方家长:
A
未实现super
B
确实类
Adapter
实现super
,以便C
可以定义MRO,当执行super(Adapter, self).__init__()
时,MRO将发挥作用如果是另一种情况呢
第三方父级:
A
实现super
B
不这里的模式相同,只是执行顺序在
Adapter.__init__
中切换super
首先调用,然后是显式调用。请注意,具有第三方父级的每种情况都需要一个唯一的适配器类虽然您可以通过使用适配器类来处理不控制
A
和B
的源代码的情况,但确实,您必须了解父类的init如何实现super
(如果有的话)相关问题 更多 >
编程相关推荐