Python:当超类接受不同的参数时,正确的初始化方法是什么?

时间:2010-10-27 02:26:10

标签: python multiple-inheritance

如果我有三个这样的课程:

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):
        ???

初始化MixinClassBaseClass的正确方法是什么?

看起来就像我可以使用super一样,因为MixinClassBaseClass都接受不同的参数......还有两个调用,{{1 }和MixinClass.__init__(...),可能会导致钻石遗传问题BaseClass.__init__(...)被设计为阻止。

2 个答案:

答案 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

但以下是这些例子:

完整示例1 ​​

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__()

完整示例2:

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)