pythonpython-2.7inheritancemultiple-inheritancemultiple-arguments

Multiple Inheritance and calling super()


I get the error: TypeError: __init__() takes exactly 2 arguments (3 given)

When trying to instantiate an object from the class Top:

super(Middle1, self).__init__(name, "middle")

class Base(object):
    def __init__(self, name, type):
        pass

class Middle1(Base):
    def __init__(self, name):
        super(Middle1, self).__init__(name, "middle1")

class Middle2(Base):
    def __init__(self, name):
        super(Middle2, self).__init__(name, "middle2")

class Middle3(Base):
    def __init__(self, name):
        super(Middle3, self).__init__(name, "middle3")

class Top(Middle1, Middle2, Middle3):
    def __init__(self):
        super(Top, self).__init__("top")

# Here is where it produces the error
if __name__ == '__main__':
    Top()

What am I not understanding about this multiple inheritance issue?

Note: this is python 2.7

EDIT

Ok so I tried something that I think works for my case. This is the equivelent end result, I think it's basically forcing depth first by not calling super and calling each individual __init__ instead.

class Base(object):
    def __init__(self, name, type):
        pass

class Middle1(Base):
    def __init__(self, name, type = "middle1"):
        super(Middle1, self).__init__(name, type)

class Middle2(Base):
    def __init__(self, name, type = "middle2"):
        super(Middle2, self).__init__(name, type)

class Middle3(Base):
    def __init__(self, name, type = "middle3"):
        super(Middle3, self).__init__(name, type)

class Top(Middle1, Middle2, Middle3):
    def __init__(self):
        Middle1.__init__(self, "top")
        Middle2.__init__(self, "top")
        Middle3.__init__(self, "top")

# No errors anymore
if __name__ == '__main__':
    Top()

Solution

  • First, you have to look at the method resolution order of Top:

    >>> for c in Top.__mro__: print c
    ...
    <class '__main__.Top'>
    <class '__main__.Middle1'>
    <class '__main__.Middle2'>
    <class '__main__.Middle3'>
    <class '__main__.Base'>
    <type 'object'>
    

    This helps you see which class each call to super represents.

    Your mistake is thinking that the call to super(Middle1, self) refers to the (only) base class Base of Middle1. It does not: it refers to the the class following Middle1 in the MRO of self.__class__. Since self.__class__ is Top, the next class in line is Middle2, whose __init__ takes only one argument.

    To use super correctly from a method, you need to ensure that the method takes the same arguments in every class, because you cannot predict which class's method will be called by looking at the code itself; it depends entirely on the type of the object that initiates the chain of calls, which might be a class you aren't even aware of yet.

    There are two posts I suggest reading:

    Together, they give you a good understanding of when super can be used correctly and how to avoid the problem you see here.

    (In full disclosure, I haven't read either post recently, so I will refrain from trying to summarize the advice presented in each.)