101

Consider the following snippet of python code

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

I am wondering how can I pass a, b and c to corresponding base classes' constructors.

vvvvv
  • 25,404
  • 19
  • 49
  • 81
Lin
  • 1,547
  • 2
  • 12
  • 26
  • 5
    Possible duplicate of [How does Python's super() work with multiple inheritance?](http://stackoverflow.com/questions/3277367/how-does-pythons-super-work-with-multiple-inheritance) – erip Jan 19 '16 at 18:46
  • you can call `__init__` twice for `B` and `C`. – sobolevn Jan 19 '16 at 18:52
  • But then `A.__init__` would be called twice, wouldn't it? – joechild Jan 19 '16 at 19:05
  • 1
    Also read about the [diamond problem](https://en.wikipedia.org/wiki/Diamond_problem) – code_dredd Jan 19 '16 at 19:22
  • 2
    [Python’s super() considered super!](https://rhettinger.wordpress.com/2011/05/26/super-considered-super/) – GingerPlusPlus Jan 19 '16 at 19:45
  • 17
    I think this is *not* a duplicate of the other famous question, because this question is dealing specifically with *how to make the super call work in the case of diamond inheritance*, while the other is a question about how *super works in general*. – shx2 Jan 19 '16 at 19:49

4 Answers4

102

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.

shx2
  • 61,779
  • 13
  • 130
  • 153
  • But another question about super(). By MRO of D, i.e., D,B,C,A, then when executing `super(D,self).__init__(a=a,b=b,c=c)`, it will call the __init__ of B. But inside constr of B it will call `super(B,self).__init__(**kw)` then by MRO of B, what follows for B is A, then it should go to call constr of A. But why instead it goes on calling constr of C instead of constr of A? Thanks, – Lin Jan 20 '16 at 18:45
  • @Lingxiao `super` calls the next method in the MRO order. The MRO is `DBCA`, which means `C` comes after `B`, despite the fact that `C` derives from `A`. That's why the classes should be designed for multiple inheritance -- when you write them, you cannot assume you know which class comes after your class in the MRO order, because that's only determined later, when your class is being used as a base in a multiple inheritance. – shx2 Jan 20 '16 at 18:55
  • This didn't work for me when had two base classes A,B and one class C(A,B) and than class D(C). Had to add super calls to A and B. Looks like you cannot omit them even if inheriting from object. – Andrzej Polis Jan 04 '17 at 13:12
  • @AndrzejPolis I don't think that is correct. I suggest you post your code in a new question. Someone would surely suggest the right way to do it in your case. – shx2 Jan 04 '17 at 16:59
  • @shx2 what would happen if I just try to pass two classes 'class A(object)' and 'class B(object)' that inherit only from object. Naive call of the 'super()' method calls only the '__init__' of A but not the one of B. – Alexander Cska Oct 17 '18 at 15:27
  • Cant we use the `D.mro()` to see the sequence? – Shivam Kotwalia Nov 25 '19 at 17:57
  • If a class is designed for mixin, and only inherits form object, even so it has to call super().__init__. When the object.__init__ is called, the **kwargs should be empty by then. – nadapez Oct 05 '21 at 01:48
32

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.

Brendan Abel
  • 35,343
  • 14
  • 88
  • 118
8

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
nadapez
  • 2,603
  • 2
  • 20
  • 26
1

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'>]
DovaX
  • 958
  • 11
  • 16
  • Heads up that this defeats one of the major advantage of using multiple inheritance, namely incorporating *new* functionality in the middle of the MRO. – MisterMiyagi Sep 06 '21 at 12:25