Summary: In this tutorial, we will go deep into the Python super() function and learn its importance in inheritance.

What is super() in Python?

Python is an OOP language, so it allows to reuse the codes by inheriting one class to another.

Consider the following code for instance:

class Person:
    def __init__(self, name, dob):
        self.name = name
        self.dob = dob
        
    def display_name(self):
        print('Name: ', self.name)
        
    def display_dob(self):
        print('DOB: ', self.dob)    
        
        
class Student:
    def __init__(self, name, dob, roll_no):
        self.name = name
        self.dob = dob
        self.roll_no = roll_no
        
    def display_name(self):
        print('Name: ', self.name)
        
    def display_dob(self):
        print('DOB: ', self.dob) 
        
    def display_rollno(self):
        print('Roll No: ', self.roll_no) 

Both of the classes here have a lot of codes in common, and because a Student is also a Person, we can use inheritance i.e. inherit the Person in the Student class and reuse its code.

class Person:
    def __init__(self, name, dob):
        self.name = name
        self.dob = dob
        
    def display_name(self):
        print('Name: ', self.name)
        
    def display_dob(self):
        print('DOB: ', self.dob)    
        
        
class Student(Person):
    def __init__(self, name, dob, rollno):
        super().__init__(name, dob)
        self.rollno = rollno
        
    def display_rollno(self):
        print('Roll No: ', self.rollno) 

As you can see, this time we had to write less code for the Student class and the code works fine, it’s because of inheritance.

Because we have inherited the Person in the Student class, its attributes and behaviors become available in the Student class.

To initialize the name and dob attributes, we have used the super() method to access and reuse the __init__ method of the parent class i.e. Person.

>>> s = Student('AK', '12/12/1999', 23)
>>> s.display_name()
Name: AK
>>> s.display_rollno()
Roll No: 23

The super method returns a temporary object of the parent class that allows us to access its attributes and methods.

We use it in the child class to access the attributes and methods of the parent class.

Why super() Function is needed?

Although the parent class’s methods become available to the child class by inheritance, sometimes we may need to redefine or extend the functionality of the methods.

For example, in the above program, we could have called the __init__ method of the parent class, but it could only have initialized name and dob. So we override it in the child class by calling super().__init__ and adding an additional set of statements.

By doing this we were able to extend the functionality of the __init__ method of the parent class.

The super function is useful in accessing the inherited methods that have been overridden in the child class.

Without the super function, we would have to rewrite all the codes into the child class’s __init__ method.

Because the display_name and display_dob methods need not be changed, we didn’t override them in the Student class.

Super() Function Parameters

The super function can accept two parameters as super(title, object):

ParameterDescription
typeThe parent or the sibling class.
objectThe instance or type of the first argument (i.e. type).

These parameters decide the order in which the base classes are searched for a member during the lookup, which is known as MRO (method resolution order).

To know what roles these parameters play, we have to deep dive into the different types of inheritance in Python.

Super() in MultiLevel Inheritance

For an example consider the following program:

class A:
    def display(self):
        print("Class A")
        
class B(A):
    def display(self):
        print("Class B")
        
class C(B):
    def display(self):
        super().display()
        print("Class C")

Here, class B inherits from class A and class C inherits from class B, which forms a multilevel inheritance.

In each child class, we are overriding the display method of the parent class.

If we invoke the display method of class C, we will get the output as follows:

>>> c = C()
>>> c.display()
Class B
Class C

As you can see, the output also contains the result from class B’s display method. This is because we have the super().display() statement in the display method of class C is invoking the display method of the parent class i.e. class B.

We can question how Python decides which parent class’s display method to call because class C has two parent classes with display methods at different levels, class A and class B.

The answer is the parameters of the super function and the method resolution order of class C.

The parameters decide the class from which the Python interpreter should look up for the display method in the method resolution order.

In the above example, we didn’t pass any parameters to the super method in super().display(). In that case, Python looks for the display method starting from the class that comes after the class C in the method resolution order i.e. class B.

We can view this order by accessing the __mro__ attribute of class C as follows:

>>> C.__mro__
(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)

Because in the order, class B comes after class C, Python looks for the display method in B and calls the same.

If class B did not have the display method, Python would proceed to look for the display method in the next class (i.e. class A) in sequence.

Therefore, here the super() is equivalent to super(C, self).

Modify Lookup in MRO using Super Function

If we want Python to invoke class A’s display method instead of class B’s, we need to instruct Python to initiate the lookup from class A in the MRO by writing super(B, self).

class C(B):
    def display(self):
        super(B, self).display()
        print("Class C")
>>> c = C()
>>> c.display()
Class A
Class C

super() in Multiple Inheritance

In addition to single and multilevel inheritance, Python also allows multiple inheritance in which a single child class inherits from multiple parent classes that do not necessarily inherit from each other.

The following picture depicts an example of multiple inheritance:

multiple inheritance

In such cases, super() method is very much useful.

For an example, consider the following program:

class LaserPrinter:
    def print(self):
        print('Printing using Laser...')
        
class InkjetPrinter:
    def print(self):
        print('Printing using Ink...')

        
class _3DPrinter(LaserPrinter, InkjetPrinter):
    def print(self):
        #codes
        super().print()

Here, the _3DPrinter is inheriting from both LaserPrinter and InkjetPrinter classes, thus implementing multiple inheritance. Also, it is extending the functionality of the print method by calling the print method of the parent class via super() method.

In the child class, we are accessing the print method of the parent class (i.e. super().print()) and because print methods are available in both the parent classes, we are not sure which print method will be called.

On calling the print method on the instance of _3DPrinter, we get the output as follows:

>>> printer = _3DPrinter()
>>> printer.print()
Printing using Laser...

From the output, it is clear that the super().print() is calling the LaserPrinter class’s print method.

If we look into the MRO of the _3DPrinter, we see that LaserPrinter comes before InkjetPrinter in the order, which is why we got that output.

>>> print(_3DPrinter.__mro__)
(<class '__main__._3DPrinter'>, <class '__main__.LaserPrinter'>, <class '__main__.InkjetPrinter'>, <class 'object'>)

Such ambiguity in the method resolution order is common in Python when we inherit multiple classes.

To fix this ambiguity, we can either use different method names in the parent classes or explicitly tell Python to look for a specific method after a particular class in the method resolution order by passing parameters in the super method.

Because InkejetPrinter is later in the method resolution order, we need to pass LaserPrinter as type and self as the object to instruct Python to look for the print method in the classes after the LaserPrinter class in the MRO.

...

class _3DPrinter(LaserPrinter, InkjetPrinter):
    def print(self):
        super(LaserPrinter, self).print()
>>> printer = _3DPrinter()
>>> printer.print()
Printing using Ink...

Because _3DPrinter also extends to LaserPrinter, we can pass self as the object of type LaserPrinter.

Conclusion

The super method returns a temporary method of the parent class that helps in accessing its attributes and methods in the child class.

It is useful in accessing the parent class’s methods which are overridden in the child class, thus allowing us to extend their functionality in the child class.

Leave a Reply