multigoodverse
multigoodverse

Reputation: 8072

Why is Python super used in the child's init method?

According to Python docs super()

is useful for accessing inherited methods that have been overridden in a class.

I understand that super refers to the parent class and it lets you access parent methods. My question is why do people always use super inside the init method of the child class? I have seen it everywhere. For example:

class Person:

    def __init__(self, name):
        self.name = name

class Employee(Person):
    def __init__(self, **kwargs):
       super().__init__(name=kwargs['name']) # Here super is being used

    def first_letter(self):
        return self.name[0]

e = Employee(name="John")
print(e.first_letter())

I can accomplish the same without super and without even an init method:

class Person:

    def __init__(self, name):
        self.name = name

class Employee(Person):

    def first_letter(self):
        return self.name[0]

e = Employee(name="John")
print(e.first_letter())

Are there drawbacks with the latter code? It looks so much cleanr to me. I don't even have to use the boilerplate **kwargs and kwargs['argument'] syntax.

I am using Python 3.8. Edit: Here's another stackoverflow questions which has code from different people who are using super in the child's init method. I don't understand why. My best guess is there's something new in Python 3.8.

Upvotes: 2

Views: 1090

Answers (3)

Steve
Steve

Reputation: 483

The potential drawback to the latter code is that there is no __init__ method within the Employee class. Since there is none, the __init__ method of the parent class is called. However, as soon as an __init__ method is added to the Employee class (maybe there's some Employee-specific attribute that needs to be initialized, like an id_number) then the __init__ method of the parent class is overridden and not called (unless super.__init__() is called) and then an Employee will not have a name attribute.

Upvotes: 1

chepner
chepner

Reputation: 532323

The correct way to use super here is for both methods to use super. You cannot assume that Person is the last (or at least, next-to-last, before object) class in the MRO.

class Person:

    def __init__(self, name, **kwargs):
        super().__init__(**kwargs)
        self.name = name

class Employee(Person):
    # Optional, since Employee.__init__ does nothing
    # except pass the exact same arguments "upstream"
    def __init__(self, **kwargs):
       super().__init__(**kwargs)

    def first_letter(self):
        return self.name[0]

Consider a class definition like

class Bar:
    ...

class Foo(Person, Bar):
    ...

The MRO for Foo looks like [Foo, Person, Bar, object]; the call to super().__init__ inside Person.__init__ would call Bar.__init__, not object.__init__, and Person has no way of knowing if values in **kwargs are meant for Bar, so it must pass them on.

Upvotes: 1

The child might want to do something different or more likely additional to what the super class does - in this case the child must have an __init__.

Calling super’s init means that you don’t have to copy/paste (with all the implications for maintenance) that init in the child’s class, which otherwise would be needed if you wanted some additional code in the child init.

But note there are complications about using super’s init if you use multiple inheritance (e.g. which super gets called) and this needs care. Personally I avoid multiple inheritance and keep inheritance to aminimum anyway - it’s easy to get tempted into creating multiple levels of inheritance/class hierarchy but my experience is that a ‘keep it simple’ approach is usually much better.

Upvotes: 3

Related Questions