Python:当父类接受不同参数时,正确的初始化方法是什么?
如果我有三个这样的类:
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
接受的参数不一样……而且如果我分别调用 MixinClass.__init__(...)
和 BaseClass.__init__(...)
,可能会导致一个叫做“菱形继承”的问题,而 super
就是为了避免这个问题而设计的。
2 个回答
为了进一步解释@chris-b的正确回答,下面是一些基于提问者使用场景的例子,以及我已经尝试过的一种模式(虽然大多数情况下都没成功,至少在代码美观方面是这样)。
回顾
简单来说,如果你在每个你写的类的init方法中调用super.__init__
,那么在使用多重继承时,Python会按照方法解析顺序(MRO)来依次调用所有类的初始化方法。super.__init__
的作用是调用parent.__init__
,并让parent.__init__
去调用它的所有兄弟类的初始化方法。
因此,对于一个简单的class C(A, B)
,B.__init__
只有在A.__init__
本身调用了super.__init__
的情况下才会被调用,即使C.__init__
使用了super.__init__
。
另一种方法是手动调用你想要的初始化方法,比如在C.__init__
中调用A.__init__(self)
和B.__init__(self)
;这种方法的缺点是,它可能会破坏未来继承的类,这些类调用super并期望所有父类的初始化方法也被调用。你必须了解各个父类的初始化方法的作用。
因此,人们可能会认为一直使用super.__init__
是正确的做法;但正如提问者所说,当不同的init方法期望不同的参数时,这种“魔法”调用链就会中断(这在使用混入模式时很常见!)。
更多信息可以在这个链接中找到。
有没有完美的解决方案?
不幸的是,似乎在Python中使用多重继承(和混入模式)需要对多个层次的情况有一定的了解;
即使尝试通过接受*args
和**kwargs
来规划扩展情况,并调用super.__init__
传递所有参数,也会失败,因为对象的init()只接受一个参数(self)!
这在下面的第一个例子中有说明。
我使用的一个非常丑陋的黑客方法是将对super.init
的调用放在try except块中,如下所示:
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__()
这似乎有效,但真的很丑陋。
我做了一个gist:https://gist.github.com/stefanocrosta/1d113a6a0c79f853c30a64afc4e8ba0a
但以防万一,这里是例子:
完整示例 1
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)
完整示例 2:
class BaseClass(object):
def __init__(self, base_arg, base_arg2=None, *args, **kwargs):
print "\tBaseClass: {}, {}".format(base_arg, base_arg2)
try:
super(BaseClass, self).__init__(*args, base_arg=base_arg, base_arg2=base_arg2, **kwargs)
except:
super(BaseClass, self).__init__()
class MixinClass(object):
def __init__(self, mixin_arg, *args, **kwargs):
print "\tMixinClass: {}".format(mixin_arg)
try:
super(MixinClass, self).__init__(*args, mixin_arg=mixin_arg, **kwargs)
except:
super(MixinClass, self).__init__()
class MixinClassB(object):
def __init__(self, mixin_arg, *args, **kwargs):
print "\tMixinClassB: {}".format(mixin_arg)
try:
super(MixinClassB, self).__init__(*args, mixin_arg=mixin_arg, **kwargs)
except:
super(MixinClassB, self).__init__()
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 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)
try:
base = BaseClass(1, 2)
except Exception as e:
print "Failed because object.__init__ does not expect any argument ({})".format(e)
childA = ChildClassA(1, 3, 2) # note the order of the arguments - the mixin arg is interleaved
childC = ChildClassC(1, 3, 2)
childD = ChildClassD(1, 3, 2)
简单来说,在Python中,你 不能 安全地支持这种类型的继承。幸运的是,你几乎不需要这样做,因为大多数方法并不关心某个东西是什么,只关心它是否支持特定的接口。最好的办法是使用组合或聚合:让你的类继承其中一个父类,同时包含对另一个类实例的引用。只要你的类支持第二个类的接口(并将消息转发给包含的实例),这通常就能正常工作。
在上面的例子中(两个类都继承自 object
),你可以(可能)安全地同时继承这两个类,并使用 MixinClass.__init__
和 BaseClass.__init__
来调用它们的构造函数。但要注意,如果父类在它们的构造函数中调用了 super
,这样做就不安全。一个好的经验法则是:如果父类使用 super
,那么你也用 super
;如果父类使用 __init__
,那么你就用 __init__
。希望你永远不要被迫从两个选择了不同初始化方法的类中继承。