6.5 Method Overriding and super() Calls in Python

In Object-Oriented Programming (OOP), method overriding allows a subclass (child class) to provide a specific implementation of a method that is already defined in its superclass (parent class). This is useful when the behavior of the inherited method needs to be customized for the subclass. The super() function enables you to call methods from the superclass, allowing you to reuse the parent class’s implementation while extending or modifying it.

In this section, we will explore the concept of method overriding, how and when to use super() calls, and the significance of these techniques in building flexible and extensible Python applications.


6.5.1 What is Method Overriding?

Method overriding occurs when a subclass provides its own implementation of a method that is already defined in the superclass. The subclass’s method will take precedence over the inherited method when the method is called on an instance of the subclass.

Key Points:

  • Overriding replaces a method from the parent class with a new implementation in the child class.
  • The overridden method in the subclass has the same name, return type, and parameters as the method in the parent class.
  • Overriding is useful when the subclass needs to provide specific functionality that differs from or extends the behavior of the superclass.

6.5.2 Method Overriding: Example

Let’s start with an example of method overriding. We’ll define a parent class Animal with a method speak() and a subclass Dog that overrides the speak() method.

Example:

class Animal:
    def speak(self):
        return "The animal makes a sound."

class Dog(Animal):
    def speak(self):
        return "The dog barks."

# Creating instances
animal = Animal()
dog = Dog()

# Calling the speak method on both instances
print(animal.speak())  # Output: The animal makes a sound.
print(dog.speak())     # Output: The dog barks.

In this example:

  • The Animal class defines the speak() method, which returns a general statement for animals.
  • The Dog class inherits from Animal but overrides the speak() method to return a specific behavior for dogs.
  • When dog.speak() is called, the overridden method in the Dog class is executed, not the method in the Animal class.

6.5.3 The super() Function

The super() function allows you to call a method from the parent class in the child class. This is especially useful when you want to extend or modify the behavior of a parent class’s method rather than completely replace it.

Syntax:

super().method_name(arguments)
  • super() returns a temporary object of the superclass that allows access to its methods.
  • You can use super() to invoke the parent class’s constructor or any of its methods.

6.5.4 Using super() in a Method

Let’s modify the previous example to use super() in the Dog class to retain the parent class's behavior while adding new functionality.

Example:

class Animal:
    def speak(self):
        return "The animal makes a sound."

class Dog(Animal):
    def speak(self):
        # Call the parent class's speak method using super()
        base_speak = super().speak()
        return base_speak + " The dog barks."

# Creating an instance of Dog
dog = Dog()

# Calling the overridden method
print(dog.speak())  # Output: The animal makes a sound. The dog barks.

In this example:

  • The Dog class overrides the speak() method.
  • The super().speak() call inside the Dog class invokes the speak() method from the parent class (Animal).
  • The child class method then extends the behavior by adding "The dog barks." to the result of super().speak().

6.5.5 Overriding the Constructor (__init__) with super()

You can also override the constructor method (__init__()) in the child class. When you override the constructor, you should call the parent class's constructor using super() to ensure that the parent class’s attributes are properly initialized.

Example:

class Vehicle:
    def __init__(self, brand, model):
        self.brand = brand
        self.model = model

    def description(self):
        return f"{self.brand} {self.model}"

class ElectricCar(Vehicle):
    def __init__(self, brand, model, battery_capacity):
        # Call the parent class's __init__ method using super()
        super().__init__(brand, model)
        self.battery_capacity = battery_capacity  # New attribute specific to ElectricCar

    def description(self):
        # Extend the parent class's description method
        return super().description() + f" with a battery capacity of {self.battery_capacity} kWh"

# Creating an instance of ElectricCar
my_car = ElectricCar("Tesla", "Model S", 100)

# Accessing methods
print(my_car.description())
# Output: Tesla Model S with a battery capacity of 100 kWh

In this example:

  • The ElectricCar class overrides the __init__() constructor to add a new attribute battery_capacity.
  • The super().__init__() call ensures that the brand and model attributes from the parent class (Vehicle) are initialized.
  • The description() method is also overridden to extend the behavior of the parent class’s description() method using super().

6.5.6 Overriding Methods: Practical Use Cases

1. Extending Methods in Specialized Subclasses

When building complex systems, subclasses often need to extend the behavior of their parent classes. Overriding allows you to modify inherited methods to provide specialized functionality for specific use cases.

Example:

class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary

    def get_salary(self):
        return f"Employee {self.name} earns {self.salary}."

class Manager(Employee):
    def __init__(self, name, salary, bonus):
        super().__init__(name, salary)
        self.bonus = bonus

    def get_salary(self):
        base_salary = super().get_salary()
        return base_salary + f" with a bonus of {self.bonus}."

In this example:

  • The Manager class inherits from Employee and extends the get_salary() method to include the manager’s bonus.
  • By using super(), the Manager class first calls the Employee class’s get_salary() method to get the base salary and then adds the bonus information.

2. Customizing Initialization in Subclasses

If a subclass requires additional initialization steps beyond what is provided by the parent class, you can override the constructor (__init__()) and use super() to build on the parent class’s initialization logic.

Example:

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

class Bird(Animal):
    def __init__(self, name, can_fly):
        super().__init__(name)
        self.can_fly = can_fly

    def info(self):
        flying_status = "can fly" if self.can_fly else "cannot fly"
        return f"{self.name} is a bird and {flying_status}."

Here, the Bird class needs additional information (can_fly) during initialization, so it overrides the __init__() method while still calling super().__init__(name) to initialize the name attribute from the parent class (Animal).


6.5.7 Method Resolution Order (MRO)

In Python, when a method is called, the interpreter uses the Method Resolution Order (MRO) to determine which method to invoke. The MRO is the order in which classes are checked for the called method when dealing with inheritance, especially multiple inheritance.

The MRO can be checked using the __mro__ attribute or the mro() method.

Example:

class A:
    def speak(self):
        print("A speaks")

class B(A):
    def speak(self):
        print("B speaks")

class C(A):
    def speak(self):
        print("C speaks")

class D(B, C):
    pass

# Creating an instance of D
d = D()
d.speak()  # Output: B speaks

# Checking the Method Resolution Order (MRO)
print(D.__mro__)
# Output: (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

In this example:

  • The MRO determines that the speak() method in class B is called before the one in class C due to the MRO order.
  • The MRO is important when dealing with multiple inheritance and method overriding, as it helps resolve conflicts.

6.5.8 Summary

  • Method overriding allows a subclass to provide its own implementation of a method that is defined in the superclass.
  • The super()

function is used to call methods from the parent class in the child class, enabling the subclass to extend or modify the behavior of the inherited methods.

  • Overriding is commonly used to customize or extend the functionality of inherited methods, making it a powerful feature in object-oriented design.
  • The Method Resolution Order (MRO) determines the order in which Python looks for a method when multiple classes are involved in inheritance.
  • Use overriding and super() to build flexible, reusable, and extensible classes in complex applications.

Method overriding and super() are key tools for building robust object-oriented systems, allowing you to extend existing functionality and write modular code that’s easy to maintain and scale.