71

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.

2
  • 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. Commented Nov 14, 2014 at 10:21
  • 1
    super() is for when you are using mixins. Don't use it when you have different signatures. Commented Nov 14, 2014 at 11:33

1 Answer 1

144

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)
Sign up to request clarification or add additional context in comments.

5 Comments

@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.
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...
@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.
I find it worth noting that for cooperative inheritance superclasses need to call super().__init__() within their __init__(). Without, the MRO chain breaks and the __init__() methods of following classes in the chain are not called.
@SaschaH: there is no actual need. That depends on the design of the classes and wether or not your subclass wants to cooperate on this or perhaps, deliberately, not cooperate in certain cases. That's not a common option but it is an option.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.