假设我有一个多重继承场景:
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__()
!这就是你的代码不能正常工作的原因。由于diamond继承在python中的工作方式,基类为object
的类不应该调用super().__init__()
。正如您所注意到的,这样做会破坏多重继承,因为您最终会调用另一个类的__init__
,而不是object.__init__()
。(免责声明:避免super().__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
一个mixin是一个设计为与多重继承一起使用的类。这意味着我们不必手动调用两个父构造函数,因为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
的代码,也调用super
,调用B
的代码。有关如何使用super
执行操作的详细信息,请参见http://rhettinger.wordpress.com/2011/05/26/super-considered-super。[回答问题,稍后编辑]
引用的文章展示了如何通过在
A
和B
周围添加包装类来处理这种情况。在题为“如何合并一个非合作类”的章节中有一个已经解决的例子。有人可能希望多重继承更容易,让您轻松地组合汽车和飞机类以获得FlyingCar,但事实是,单独设计的组件通常需要适配器或包装器,然后才能像我们希望的那样无缝地装配在一起:-)
另一个想法是:如果您对使用多重继承组合功能不满意,可以使用组合来完全控制在哪些情况下调用哪些方法。
如果您控制了
A
和B
的源代码,那么无论是“新样式”还是“旧样式”都可以工作。否则,可能需要使用适配器类。可访问源代码:正确使用“new style”
在这里,方法解析顺序(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链继续。你可以说这个病例是为多重遗传而设计的。
可访问源代码:正确使用“旧样式”
这里,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__()
时起作用。如果相反呢?
第三方父项:
A
实现super
;B
不这里的模式相同,只是执行顺序是在
Adapter.__init__
;super
调用中切换的,然后是显式调用。请注意,对于第三方父级的每个情况,都需要一个唯一的适配器类。尽管您可以通过使用适配器类来处理不控制源代码
A
和B
的情况,但是您必须知道父类的in it是如何实现super
(如果有的话)的。相关问题 更多 >
编程相关推荐