如果我有三个这样的课程:
class BaseClass(object):
def __init__(self, base_arg, base_arg2=None):
...
class MixinClass(object):
def __init__(self, mixin_arg):
...
class ChildClass(BaseClass, MixinClass):
def __init__(self, base_arg, mixin_arg, base_arg2=None):
???
初始化MixinClass
和BaseClass
的正确方法是什么?
它看起来就像我可以使用super
一样,因为MixinClass
和BaseClass
都接受不同的参数......还有两个调用,{{1 }和MixinClass.__init__(...)
,可能会导致钻石遗传问题BaseClass.__init__(...)
被设计为阻止。
答案 0 :(得分:13)
基本上,在Python中,you can't support this type of inheritance safely。幸运的是,你几乎从不需要,因为大多数方法不关心什么是什么,只是它支持特定的接口。您最好的选择是使用合成或聚合:让您的类继承其中一个父类,并包含对第二个类的实例的引用。只要你的类支持第二个类的接口(并将消息转发到包含的实例),这可能会很好。
在上面的示例中(两个类都继承自object
),您可以(可能)安全地从两个类继承并使用MixinClass.__init__
和BaseClass.__init__
调用两个构造函数。但请注意,如果父类在的构造函数中调用super
,则不安全。一个好的经验法则是,如果父类使用super
,则使用super
;如果父类使用__init__
,则使用__init__
,并希望您永远不会被困继承自两个选择不同初始化方法的类。
答案 1 :(得分:0)
只是为了扩展@ chris-b的正确答案,下面是一些基于OP用例的示例和我已经尝试过的模式(并且大多数都失败了,至少根据代码之美而言)。
总结一下,如果你在你编写的每个类 init 中调用super.__init__
,python将很好地跟随MRO在多重继承时调用所有类的所有inits。用过的。
super.__init__
正在调用parent.__init__
,并委派parent.__init__
来调用其所有兄弟姐妹。
因此,对于简单的class C(A, B)
,只有在B.__init__
本身调用A.__init__
时才会调用super.__init__
,即使C.__init__
使用{{1} }}
另一种方法是手动调用所需的inits,例如。 super.__init__
中的A.__init__(self)
和B.__init__(self)
;缺点是这种模式可能会打破调用super的未来继承类,并期望调用所有父inits。必须/知道/各种父母的行为。
因此,人们会认为始终使用C.__init__
是正确的事情;但是正如OP所说的那样,当不同的 init (混合模式中常见的东西!)预期不同的参数时,这个“神奇的”调用链断开。
可以在How does Python's super() work with multiple inheritance?
中找到更多信息不幸的是。似乎在Python中使用多重继承(和mixin模式)需要了解多个级别的内容;
即使尝试通过接受super.__init__
和*args
来计划扩展案例,并且调用**kwargs
传递所有参数也会失败,因为对象。 init ()仅接受一个参数(自我)!
以下第一个例子显示了这一点。
我一直在使用的非常丑陋的黑客攻击(虽然可能不适用于所有可能的情况)是在try中包含调用super。 init ,除了以下块:
super.__init__
这似乎有效 - 但真的很难看。
我已经提出了一个要点:https://gist.github.com/stefanocrosta/1d113a6a0c79f853c30a64afc4e8ba0a
但以下是这些例子:
try:
super(ThisClass, self).__init__(*args, arg1=arg1, arg2=arg2, **kwargs)
except TypeError as e:
# let's hope this TypeError is due to the arguments for object...
super(ThisClass, self).__init__()
class BaseClass(object):
def __init__(self, base_arg, base_arg2=None, *args, **kwargs):
print "\tBaseClass: {}, {}".format(base_arg, base_arg2)
super(BaseClass, self).__init__(*args, base_arg=base_arg, base_arg2=base_arg2, **kwargs)
class MixinClass(object):
def __init__(self, mixin_arg, *args, **kwargs):
print "\tMixinClass: {}".format(mixin_arg)
super(MixinClass, self).__init__()
class MixinClassB(object):
def __init__(self, mixin_arg, *args, **kwargs):
print "\tMixinClassB: {}".format(mixin_arg)
super(MixinClassB, self).__init__(*args, mixin_arg=mixin_arg, **kwargs)
class ChildClassA(BaseClass, MixinClass):
"""
Let's make it work for this case
"""
def __init__(self, base_arg, mixin_arg, base_arg2=None):
print "Initializing {}: base_arg: {} mixin_arg: {} base_arg2: {}".format(
self.__class__.__name__, base_arg, mixin_arg, base_arg2)
super(ChildClassA, self).__init__(base_arg=base_arg, mixin_arg=mixin_arg, base_arg2=base_arg2)
class ChildClassB(BaseClass, MixinClass):
"""
Same as above, but without specifying the super.__init__ arguments names
"""
def __init__(self, base_arg, mixin_arg, base_arg2=None):
print "Initializing {}: base_arg: {} mixin_arg: {} base_arg2: {}".format(
self.__class__.__name__, base_arg, mixin_arg, base_arg2)
# If you don't specify the name of the arguments, you need to use the correct order of course:
super(ChildClassB, self).__init__(base_arg, base_arg2, mixin_arg)
class ChildClassC(BaseClass, MixinClassB, MixinClass):
"""
Now let's simply add another mixin: before...
"""
def __init__(self, base_arg, mixin_arg, base_arg2=None):
print "Initializing {}: base_arg: {} mixin_arg: {} base_arg2: {}".format(
self.__class__.__name__, base_arg, mixin_arg, base_arg2)
super(ChildClassC, self).__init__(base_arg=base_arg, mixin_arg=mixin_arg, base_arg2=base_arg2)
class ChildClassD(BaseClass, MixinClass, MixinClassB):
"""
Now let's simply add another mixin: ..and after
"""
def __init__(self, base_arg, mixin_arg, base_arg2=None):
print "Initializing {}: base_arg: {} mixin_arg: {} base_arg2: {}".format(
self.__class__.__name__, base_arg, mixin_arg, base_arg2)
super(ChildClassD, self).__init__(base_arg=base_arg, mixin_arg=mixin_arg, base_arg2=base_arg2)
childA = ChildClassA(1, 3, 2) # note the order of the arguments - the mixin arg is interleaved
childB = ChildClassB(1, 3, 2)
childC = ChildClassC(1, 3, 2)
childD = ChildClassD(1, 3, 2)