【问题标题】:python multiple inheritance passing arguments to constructors using superpython多重继承使用super将参数传递给构造函数
【发布时间】:2016-04-25 09:15:00
【问题描述】:

考虑以下python代码的sn-p

class A(object):
    def __init__(self, a):
        self.a = a

class B(A):
    def __init__(self, a, b):
        super(B, self).__init__(a)
        self.b = b

class C(A):
    def __init__(self, a, c):
        super(C, self).__init__(a)
        self.c = c

class D(B, C):
    def __init__(self, a, b, c, d):
        #super(D,self).__init__(a, b, c) ???
        self.d = d

我想知道如何将abc 传递给相应的基类的构造函数。

【问题讨论】:

标签: python multiple-inheritance super diamond-problem


【解决方案1】:

不幸的是,如果不更改基类,就无法使用super() 完成这项工作。对BC 的构造函数的任何调用都将尝试调用Method Resolution Order 中的下一个类,它始终是BC,而不是A 类,@987654328 @ 和 C 类构造函数假设。

另一种方法是显式调用构造函数,而不在每个类中使用super()

class A(object):
    def __init__(self, a):
        object.__init__()
        self.a = a

class B(A):
    def __init__(self, a, b):
        A.__init__(self, a)
        self.b = b

class C(A):
    def __init__(self, a, c):
        A.__init__(self, a)
        self.c = c

class D(B, C):
    def __init__(self, a, b, c, d):
        B.__init__(self, a, b)
        C.__init__(self, a, c)
        self.d = d 

这里还有一个缺点,因为 A 构造函数会被调用两次,这在本示例中实际上并没有太大的影响,但可能会导致更复杂的构造函数出现问题。您可以包含一个检查以防止构造函数多次运行。

class A(object):
    def __init__(self, a):
        if hasattr(self, 'a'):
            return
        # Normal constructor.

有人会说这是super()的缺点,从某种意义上来说,这也只是多继承的缺点。菱形继承模式通常容易出错。他们的许多变通方法会导致代码更加混乱和容易出错。有时,最好的答案是尝试重构代码以使用更少的多重继承。

【讨论】:

    【解决方案2】:

    嗯,一般来说,在处理多重继承时,您的基类(不幸的是)应该为多重继承而设计。您的示例中的 BC 类不是,因此您找不到在 D 中应用 super 的正确方法。

    为多重继承设计基类的常用方法之一是让中级基类在其__init__ 方法中接受他们不打算使用的额外参数,并将它们传递给他们的super 来电。

    这是在 python 中执行此操作的一种方法:

    class A(object):
        def __init__(self,a):
            self.a=a
    
    class B(A):
        def __init__(self,b,**kw):
            self.b=b
            super(B,self).__init__(**kw)
    
     class C(A):
        def __init__(self,c,**kw):
            self.c=c
            super(C,self).__init__(**kw)
    
    class D(B,C):
        def __init__(self,a,b,c,d):
            super(D,self).__init__(a=a,b=b,c=c)
            self.d=d
    

    这可以被视为令人失望,但事实就是如此。

    【讨论】:

    • 但是关于 super() 的另一个问题。通过D的MRO,即D,B,C,A,那么在执行super(D,self).__init__(a=a,b=b,c=c)的时候,会调用B的init,但是在B的constr里面会调用super(B,self).__init__(**kw)然后通过B的MRO,B的后面是A,那么它应该去调用A的constr。但是为什么它继续调用C的constr而不是A的constr?谢谢,
    • @Lingxiao super 调用 MRO 顺序中的下一个方法。 MRO 是DBCA,这意味着CB 之后,尽管C 派生自A。这就是为什么类应该被设计为多重继承的原因——当你编写它们时,你不能假设你知道在 MRO 顺序中哪个类在你的类之后,因为这只是在以后确定的,当你的类被用作一个基础时多重继承。
    • 当我有两个基类 A、B 和一个类 C(A,B) 而不是类 D(C) 时,这对我不起作用。必须向 A 和 B 添加超级调用。看起来即使从对象继承也不能省略它们。
    • @AndrzejPolis 我不认为这是正确的。我建议您在一个新问题中发布您的代码。有人肯定会在您的情况下建议正确的方法。
    • @shx2 如果我只是尝试传递仅从对象继承的两个类 'class A(object)' 和 'class B(object)' 会发生什么。 'super()' 方法的天真调用仅调用 A 的 'init' 而不是 B 的那个。
    【解决方案3】:

    我对这里的答案并不完全满意,因为有时使用不同的参数分别为每个基类调用 super() 而不重构它们会非常方便。因此,我创建了一个名为 multinherit 的包,您可以使用该包轻松解决此问题。 https://github.com/DovaX/multinherit

    from multinherit.multinherit import multi_super
    
    class A(object):
        def __init__(self, a):
            self.a = a
            print(self.a)
    
    
    class B(A):
        def __init__(self, a, b):
            multi_super(A,self,a=a)
            self.b = b
            print(self.b)
    
    
    class C(A):
        def __init__(self, a, c):
            multi_super(A,self,a=a)
            self.c = c
            print(self.c)
    
    
    class D(B, C):
        def __init__(self, a, b, c, d):
            multi_super(B,self,a=a,b=b)
            multi_super(C,self,a=a,c=c)
            self.d = d
            print(self.d)
       
    
    print()
    print("d3")            
    d3=D(1,2,3,4)
    print(d3._classes_initialized)
    
    >>> d3
    >>> 1
    >>> 2
    >>> 3
    >>> 4
    >>> [<class '__main__.B'>, <class '__main__.A'>, <class '__main__.C'>]
    

    【讨论】:

    • 请注意,这破坏了使用多重继承的主要优势之一,即在 MRO 中间合并 new 功能。
    【解决方案4】:

    一个key的概念:super不指父类。它指的是 mro 列表中的下一个类,这取决于被实例化的实际类。

    所以在调用super().__init__时,实际调用的方法是从调用帧中不确定的。

    这就是为什么必须为 mixin 专门设计这些类的原因。

    即使是一个只继承自object 的类,也应该调用super().__init__。 当然,当object__init__(**kwargs) 被调用时,kwargs 应该是空的;否则会引发错误。

    例子:

    class AMix:
        def __init__(self, a, **kwargs):
            super().__init__(**kwargs)
            self.a = a
    
              
    class BMix:
        def __init__(self, b, **kwargs):
            super().__init__(**kwargs)
            self.b = b
        
              
    class AB(AMix, BMix):
        def __init__(self, a, b):
            super().__init__(a=a, b=b)
          
          
    ab = AB('a1', 'b2')
    
    print(ab.a, ab.b)  #  -> a1 b2
    

    【讨论】:

      猜你喜欢
      • 2015-05-04
      • 1970-01-01
      • 2014-10-11
      • 1970-01-01
      • 2021-07-02
      • 1970-01-01
      • 2011-04-12
      • 2019-09-25
      相关资源
      最近更新 更多