4.2. Inheritance and Polymorphism#

Inheritance and polymorphism are powerful concepts in object-oriented programming that enable code reuse, extensibility, and flexibility. They enhance the structure and functionality of classes by allowing you to create hierarchies of classes and interact with different classes in a unified way. Let’s dive into the intricacies of inheritance and polymorphism [Lin et al., 2022, Wilson, 2022]:

4.2.1. Introduction to Inheritance#

Inheritance is a fundamental concept that enables the creation of new classes (subclasses or derived classes) based on existing ones (superclasses or base classes). This process allows the new class to inherit attributes and methods from the existing class, promoting code reuse and efficient development [Lin et al., 2022, Wilson, 2022].

4.2.2. Superclass and Subclass Relationships#

In the context of inheritance, the existing class is referred to as the superclass or base class. The new class being created is called the subclass or derived class. The subclass inherits attributes and methods from the superclass, and it can also have additional attributes and methods specific to its own purpose. The subclass extends and refines the functionality of the superclass [Lin et al., 2022, Wilson, 2022].

Example - Inheritance and Subclass Definition:

# Define a base class called Vehicle
class Vehicle:
    def __init__(self, brand):
        self.brand = brand
    
    def start_engine(self):
        # Method to start the vehicle's engine
        print(f"The {self.brand} vehicle's engine is running.")

# Define a subclass Car that inherits from Vehicle
class Car(Vehicle):
    def __init__(self, brand, model):
        # Initialize the Car with a brand and model
        super().__init__(brand)
        self.model = model
    
    def start_engine(self):
        # Method to start the car's engine, overriding the parent class method
        print(f"The {self.brand} {self.model} is revving up.")

# Creating instances of Vehicle and Car
vehicle = Vehicle("Generic")
car = Car("Toyota", "Camry")

# Using the start_engine method
vehicle.start_engine()  # Output: The Generic vehicle's engine is running.
car.start_engine()      # Output: The Toyota Camry is revving up.
The Generic vehicle's engine is running.
The Toyota Camry is revving up.

In this example:

  • The Vehicle class is defined with an __init__ method to initialize the brand attribute and a start_engine method to print a message indicating that the engine of the vehicle is running.

  • The Car class is defined as a subclass of Vehicle. It has its own __init__ method that takes both brand and model as parameters. It uses the super() function to call the parent class’s __init__ method and initializes the model attribute. It also overrides the start_engine method to print a message indicating that the specific car model is revving up.

  • Instances of both Vehicle and Car are created: vehicle and car.

  • The start_engine method is called on both instances. For the vehicle instance, the message is generic, whereas for the car instance, the specific car model is mentioned.

This example showcases the concept of inheritance, where the Car class inherits attributes and methods from its parent class Vehicle. It also demonstrates how method overriding works, as the Car class provides its own implementation of the start_engine method that’s specific to cars.

Note

In the provided example, the super() function is used within the Car class’s __init__ method to call the constructor of its superclass, which is the Vehicle class. The purpose of using super() is to ensure that the initialization code from the parent class is executed before adding any additional attributes or behavior specific to the subclass.

class Vehicle:
    def __init__(self, brand):
        self.brand = brand
    
    # Other methods...

class Car(Vehicle):
    def __init__(self, brand, model):
        super().__init__(brand)  # Calls the parent class constructor
        self.model = model
    
    # Other methods...

In this code, when you create an instance of the Car class, the __init__ method of the Car class is executed. Within this method, the super().__init__(brand) line is responsible for calling the __init__ method of the parent class (Vehicle). By doing so, it initializes the brand attribute inherited from the Vehicle class.

Using super() is particularly useful when working with inheritance, as it ensures that the common attributes and behavior from the parent class are properly initialized before adding any specific attributes or behavior to the subclass. It helps maintain a consistent object initialization process across the inheritance hierarchy.

Note that the start_engine method in the Car subclass overrides the start_engine method of the parent class Vehicle. This is a common feature in object-oriented programming called method overriding. Method overriding allows a subclass to provide a specific implementation for a method that is already defined in its parent class. When you define a method with the same name in a subclass, it “overrides” the method with the same name in the parent class. This means that when you call start_engine on an instance of the Car class, the overridden method in Car will be executed instead of the one in Vehicle.

4.2.3. Polymorphism#

Polymorphism is the ability of different classes to be treated as instances of a common class or interface. This enables a unified approach to interacting with objects of different types. By implementing the same method names in multiple classes, you can call these methods on different objects and achieve different behaviors based on the object type [Lin et al., 2022, Wilson, 2022].

Example - Polymorphism:

# Define an abstract class called Shape
class Shape:
    def area(self):
        # This method is meant to be overridden by subclasses
        pass

# Define a Circle class that inherits from Shape
class Circle(Shape):
    def __init__(self, radius):
        # Initialize the Circle with a radius
        self.radius = radius
    
    def area(self):
        # Calculate and return the area of the Circle
        return 3.14 * self.radius * self.radius

# Define a Rectangle class that inherits from Shape
class Rectangle(Shape):
    def __init__(self, length, width):
        # Initialize the Rectangle with a length and width
        self.length = length
        self.width = width
    
    def area(self):
        # Calculate and return the area of the Rectangle
        return self.length * self.width

# Using polymorphism with different shapes
shapes = [Circle(5), Rectangle(4, 6)]

for shape in shapes:
    # Calculate and print the area of each shape
    print(f"Area: {shape.area()}")
Area: 78.5
Area: 24

Here’s a breakdown of the example and how it illustrates polymorphism:

  1. Base Class and Method Definition:

    • The Shape class serves as the base class with a single method named area. This method is not implemented in the Shape class (it contains a pass statement), as the actual implementation will be provided by its subclasses.

  2. Subclasses and Method Implementation:

    • The Circle and Rectangle classes inherit from the Shape class.

    • Each subclass (Circle and Rectangle) defines its own implementation of the area method to calculate the area based on their specific attributes (radius for Circle and length and width for Rectangle).

  3. Using Polymorphism:

    • In the last part of the code, a list named shapes is created, containing instances of both the Circle and Rectangle classes.

    • The for loop iterates through each shape in the shapes list.

    • Regardless of whether the shape is a Circle or a Rectangle, the loop treats each shape as an instance of the common base class Shape.

    • The area method is called on each shape, and the appropriate implementation from the respective subclass is invoked.

  4. Output:

    • The loop prints the calculated area for each shape, even though they have different implementations for the area method.

    • This demonstrates that different classes (subclasses) can be treated uniformly through a common interface (Shape’s area method), showcasing polymorphism.

Inheritance and Polymorphism Summary

Inheritance facilitates the creation of class hierarchies, promoting code reuse and extension. It establishes relationships between superclasses and subclasses, allowing the subclass to inherit and customize behavior. Polymorphism, on the other hand, enables a unified interaction with diverse objects, offering flexibility and ease of use. These concepts collectively enhance the modularity, reusability, and maintainability of object-oriented programs.