Search code examples
pythonmultiple-inheritance

Multiple inheritance from classes sharing a parent


Is it possible to inherit from multiple classes that share a parent?

class A:
    def __init__(self, a1, a2='a2'):
        self.a1=a1
        self.a2=a2
        
    def report(self):
        print('here are my attrs: ', self.a1, self.a2)

class B(A):
    def __init__(self, b, *args, **kwargs):
        self.b=b
        A.__init__(self, *args, **kwargs)


class C(A):
    def __init__(self, *args, c='c', **kwargs):
        self.c=c
        A.__init__(self, *args, **kwargs)

class D(B, C):
    def __init__(self, *args, d='d', **kwargs):
        self.d=d
        super().__init__(self, *args, **kwargs)

If I try the above example I get: TypeError: __init__() got multiple values for argument

I assume the issue arises due to *args and **kwargs from the shared parent class being duplicated when super() is resolving the MRO.

For context, I am trying to create a class that inherits methods and attributes from two other classes that share a parent.   If this is possible, how do you reconcile the different classes __init__ constructor?


Solution

  • The reason why the interpreter complains about __init__ getting multiple values for argument b is because you're calling B.__init__ from D.__init__ with both a positional argument corresponding to b and a keyword argument of b in **kwargs with this statement:

    super().__init__(self, *args, **kwargs)
    

    When you call a method through super(), self is already implicitly passed for you as the first argument to the method so your explicit argument self here actually becomes the next positional argument, b.

    Your code would work by removing self from all calls to the __init__ method of the parent. Also note that you should call super().__init__ instead of A.__init__ in both B.__init__ and C.__init__ to make them cooperatively pass the call to the next __init__ method in the MRO:

    class A:
        def __init__(self, a1, a2='a2'):
            self.a1 = a1
            self.a2 = a2
    
        def report(self):
            print('here are my attrs:', self.a1, self.a2)
    
    
    class B(A):
        def __init__(self, b, *args, **kwargs):
            print(kwargs)
            self.b = b
            super().__init__(*args, **kwargs)
    
    
    class C(A):
        def __init__(self, *args, c='c', **kwargs):
            print(kwargs)
            self.c = c
            super().__init__(*args, **kwargs)
    
    
    class D(B, C):
        def __init__(self, *args, d='d', **kwargs):
            print(kwargs)
            self.d = d
            super().__init__(*args, **kwargs)
    
    D(a1=0, a2=1, b=2, c=3, d=4).report()
    

    This outputs:

    {'a1': 0, 'a2': 1, 'b': 2, 'c': 3}
    {'a1': 0, 'a2': 1, 'c': 3}
    {'a1': 0, 'a2': 1}
    here are my attrs: 0 1