python 使用 super 向构造函数传递参数的多重继承

python multiple inheritance passing arguments to constructors using super

考虑以下 python 代码片段

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 传递给相应的基础 类' 构造函数。

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

另一种方法是在每个 class.

中显式调用构造函数而不使用 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()的缺点,从某种意义上说,这也是多重继承的缺点。菱形继承模式往往容易出错。并且许多针对它们的解决方法会导致更加混乱和 error-prone 代码。有时,最好的答案是尝试重构您的代码以使用更少的多重继承。

好吧,在处理一般的多重继承时,您的基础 类(不幸的是)应该设计用于多重继承。 类 BC 在你的例子中不是,因此你找不到在 D.[=17 中应用 super 的正确方法=]

为多重继承设计基础 类 的一种常见方法是让 middle-level 基础 类 在其 __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() 而不重构它们会变得非常方便。因此,我创建了一个名为 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'>]

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

所以当调用super().__init__时,调用的实际方法未从调用框架中确定。

这就是 classes 必须专门为 mixin 设计的原因。

即使 class 仅继承自 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