Python 多重继承使用 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传递给相应的基类的构造函数。

51003 次浏览

Unfortunately, there is no way to make this work using super() without changing the Base classes. Any call to the constructors for B or C is going to try and call the next class in the Method Resolution Order, which will always be B or C instead of the A class that the B and C class constructors assume.

The alternative is to call the constructors explicitly without the use of super() in each class.

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

There is still a downside here as the A constructor would be called twice, which doesn't really have much of an effect in this example, but can cause issues in more complex constructors. You can include a check to prevent the constructor from running more than once.

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

Some would call this a shortcoming of super(), and it is in some sense, but it's also just a shortcoming of multiple inheritance in general. Diamond inheritance patterns are often prone to errors. And a lot of the workarounds for them lead to even more confusing and error-prone code. Sometimes, the best answer is to try and refactor your code to use less multiple inheritance.

Well, when dealing with multiple inheritance in general, your base classes (unfortunately) should be designed for multiple inheritance. Classes B and C in your example aren't, and thus you couldn't find a proper way to apply super in D.

One of the common ways of designing your base classes for multiple inheritance, is for the middle-level base classes to accept extra args in their __init__ method, which they are not intending to use, and pass them along to their super call.

Here's one way to do it in 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

This can be viewed as disappointing, but that's just the way it is.

I was not completely satisfied with the answers here, because sometimes it gets quite handy to call super() for each of the base classes separately with different parameters without restructuring them. Hence, I created a package called multinherit and you can easily solve this issue with the package. 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'>]

A key concept: super does not refer to the parent class. It refers to the next class in the mro list, which depends on the actual class being instantiated.

So when calling super().__init__, the actual method called is undetermined from the calling frame.

That's why the classes have to be specially designed for mixin.

Even a class witch inherits only from object, should call super().__init__. And of course, when object__init__(**kwargs) is called, kwargs should be empty by then; else case an error will raise.

Example:

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