【问题标题】:Python: Correct way to initialize when superclasses accept different arguments?Python:当超类接受不同的参数时,正确的初始化方法?
【发布时间】:2010-10-27 02:26:09
【问题描述】:

如果我有这样的三个类:

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 都接受不同的参数......而且两个调用,MixinClass.__init__(...)BaseClass.__init__(...),将可能会导致钻石继承问题super旨在防止。

【问题讨论】:

  • 抱歉,我没看到钻石。多条路径继承什么基类?
  • object 在多个路径之间继承。
  • 正如 Ned 所说,在这种情况下它是 object,但实际上它可能是任何东西。

标签: python multiple-inheritance


【解决方案1】:

基本上,在 Python 中,you can't support this type of inheritance safely。幸运的是,您几乎不需要这样做,因为大多数方法并不关心 什么 东西是什么,只关心它支持特定的接口。您最好的选择是使用组合或聚合:让您的类继承自其中一个父类,并包含对第二个类的实例的引用。只要您的类支持第二类的接口(并将消息转发到包含的实例),这可能会很好。

在上面的示例中(两个类都继承自 object),您可以(可能)安全地从两个类继承并使用 MixinClass.__init__BaseClass.__init__ 调用两个构造函数。但请注意,如果父类在 它们的 构造函数中调用 super,这样做是不安全的。一个好的经验法则是如果父类使用super,则使用super,如果父类使用__init__,则使用__init__,并希望您永远不会被困在必须从选择不同方法的两个类中继承用于初始化。

【讨论】:

    【解决方案2】:

    只是为了扩展@chris-b 的正确答案,这里遵循一些基于 OP 用例和我已经尝试过的模式的示例(并且大部分都失败了,至少在代码美感方面)。

    回顾

    总而言之,如果您在编写的每个类init 中调用super.__init__,python 将很好地遵循 MRO 来调用所有类的所有 init,当使用多重继承时。 super.__init__ 工作调用parent.__init__,并委托parent.__init__ 调用它的所有兄弟姐妹。

    因此,对于一个简单的class C(A, B),只有在A.__init__ 本身调用super.__init__ 时才会调用B.__init__,即使C.__init__ 使用super.__init__

    另一种方法是手动调用所需的 inits,例如。 A.__init__(self)B.__init__(self)C.__init__;缺点是这种模式可能会破坏将来调用 super 的继承类,并期望所有父 inits 也被调用。必须 / 知道 / 各种父级 init 的作用。

    因此人们会认为一直使用super.__init__ 是正确的做法;但正如 OP 所述,当不同的 init 期望不同的参数时,这种“神奇”的调用链会中断(mixin 模式的常见问题!)。

    更多信息可以在How does Python's super() work with multiple inheritance?找到

    有完美的解决方案吗?

    不幸的是。似乎在 Python 中使用多重继承(和 mixin 模式)需要对多个级别发生的事情有一定的了解;

    即使尝试通过接受*args**kwargs 并调用super.__init__ 传递所有参数来计划扩展案例也会失败,因为object.init() 只接受一个参数(self)!

    这在后面的第一个示例中显示。

    我一直在使用的一个非常丑陋的技巧(尽管可能不适用于所有可能的情况)是将对 super.init 的调用包装在 try 中,但以下块除外:

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

    这似乎可行 - 但真的很难看。

    我做了一个要点: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 2 代码,它在 python 3 中不起作用
    • @guhur 这个问题是通过 Python 2 示例提出的......
    • 对,这不是对您的回答的批评,而是对未来读者的警告。抱歉,我的信息很苛刻。
    • 感谢您的澄清,理解和好点!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-02
    • 1970-01-01
    • 2019-10-24
    • 2019-10-03
    • 2017-09-09
    相关资源
    最近更新 更多