70

I have three classes: A, B and C.

C inherits from A and B (in this order). The constructor signatures of A and B are different. How can I call the __init__ methods of both parent classes?

My endeavour in code:

class A(object):
    def __init__(self, a, b):
        super(A, self).__init__()
        print('Init {} with arguments {}'.format(self.__class__.__name__, (a, b)))

class B(object):
    def __init__(self, q):
        super(B, self).__init__()
        print('Init {} with arguments {}'.format(self.__class__.__name__, (q)))

class C(A, B):
    def __init__(self):
        super(A, self).__init__(1, 2)
        super(B, self).__init__(3)

c = C()

yields the error:

Traceback (most recent call last):
  File "test.py", line 16, in <module>
    c = C()
  File "test.py", line 13, in __init__
    super(A, self).__init__(1, 2)
TypeError: __init__() takes 2 positional arguments but 3 were given

I found this resource which explains mutiple inheritance with different set of arguments, but they suggest to use *args and **kwargs to use for all argument. I consider this very ugly, since I cannot see from the constructor call in the child class what kind of parameters I pass to the parent classes.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Nikolai Tschacher
  • 1,639
  • 2
  • 17
  • 24
  • 2
    You're using `super` wrong, that should just be `super(C, self).__init__` in `C.__init__`. As you have different signatures, you have no choice but to handle arbitrary `*args, **kwargs` if you want to use `super`. – jonrsharpe Nov 14 '14 at 10:21
  • 1
    `super()` is for when you are using mixins. Don't use it when you have different signatures. – Lennart Regebro Nov 14 '14 at 11:33

1 Answers1

142

Do not use super(baseclass, ...) unless you know what you are doing. The first argument to super() tells it what class to skip when looking for the next method to use. E.g. super(A, ...) will look at the MRO, find A, then start looking for __init__ on the next baseclass, not A itself. For C, the MRO is (C, A, B, object), so super(A, self).__init__ will find B.__init__.

For these cases, you don't want to use cooperative inheritance but directly reference A.__init__ and B.__init__ instead. super() should only be used if the methods you are calling have the same signature or will swallow unsupported arguments with *args and **vargs. In that case just the one super(C, self).__init__() call would be needed and the next class in the MRO order would take care of chaining on the call.

Putting it differently: when you use super(), you can not know what class will be next in the MRO, so that class better support the arguments you pass to it. If that isn't the case, do not use super().

Calling the base __init__ methods directly:

class A(object):
    def __init__(self, a, b):
        print('Init {} with arguments {}'.format(self.__class__.__name__, (a, b)))

class B(object):
    def __init__(self, q):
        print('Init {} with arguments {}'.format(self.__class__.__name__, (q)))

class C(A, B):
    def __init__(self):
        # Unbound functions, so pass in self explicitly
        A.__init__(self, 1, 2)
        B.__init__(self, 3)

Using cooperative super():

class A(object):
    def __init__(self, a=None, b=None, *args, **kwargs):
        super().__init__(*args, **kwargs)
        print('Init {} with arguments {}'.format(self.__class__.__name__, (a, b)))

class B(object):
    def __init__(self, q=None, *args, **kwargs):
        super().__init__(*args, **kwargs)
        print('Init {} with arguments {}'.format(self.__class__.__name__, (q)))

class C(A, B):
    def __init__(self):
        super().__init__(a=1, b=2, q=3)
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 1
    @NikolaiTschacher: how else would you ask for the method on the *next* class in the MRO, without knowing what that class might be? You always know the **current** class you are defining the method for, however. – Martijn Pieters Nov 14 '14 at 10:34
  • I thought that super() without any class as argument just walks up MRO as it was defined in the subclass class statement: MRO for class A(B, C, D) is A,B,C,D. Furthermore I assumed that calling super(A, self) with an explicit Argument just visits the class that was provided in the super call and that the caller needs to check that any other parent classes are constructed correctly. So super(A, self) in my mindset would only ever visit class A, nothing else. I practically thought that A.__init__(self) is equivalent to super(A, self). Additionally I haven't fully understood super() I guess... – Nikolai Tschacher Nov 14 '14 at 10:54
  • 3
    @NikolaiTschacher: It helps if you understood what problem `super()` is trying to solve: calling the *next* method in the order in a complex hierarchy with diamond pattern inheritance (a base class used by multiple subclasses). In such cases using explicit `BaseClass.__init__` calls becomes error prone and you end up with multiple calls to `SharedBaseClass.__init__`. By using the MRO instead you have a guaranteed order, but that order is *dynamic* based on inheritance. This means you should not care about what the next class is, just make sure that any of them can accept that call. – Martijn Pieters Nov 14 '14 at 11:32