Python Inheritance

Python Inheritance is a powerful mechanism that contributes to the elegance and efficiency of object-oriented programming. By grasping its syntax....

Python, a versatile and powerful programming language, provides a rich set of features to simplify code structure and enhance reusability. One such feature is inheritance, a fundamental concept in object-oriented programming. 

Python Inheritance

In this article, we’ll delve into the world of Python inheritance, Types of Inheritance in python, exploring its principles and practical examples to solidify your understanding.

Understanding Python Inheritance

Inheritance is a core concept in object-oriented programming (OOP) that allows a class (subclass/child) to inherit attributes and methods from another class (superclass/parent). This fosters code reusability and enhances the structure of the code.

Syntax of Inheritance in Python

In Python, creating a subclass involves specifying the superclass in the class declaration.

class ParentClass:

    # Parent class attributes and methods

class ChildClass(ParentClass):

    # Child class attributes and methods

Types of Inheritance

In Python, there are four types of inheritance:

1. Single Inheritance

In a single inheritance scenario, a subclass inherits from a single superclass. Here's an example in Python:

# Define a superclass 'Shape'

class Shape:

    def __init__(self, color):

        self.color = color

    def draw(self):

        print(f"Drawing a shape with color {self.color}")

# Define a subclass 'Circle' that inherits from 'Shape'

class Circle(Shape):

    def __init__(self, color, radius):

        # Call the superclass constructor using super()

        super().__init__(color)

        self.radius = radius

    def draw(self):

        # Override the draw method in the subclass

        print(f"Drawing a circle with color {self.color} and radius {self.radius}")

# Create an instance of 'Circle'

my_circle = Circle("Blue", 5)

# Access methods from both the superclass and the subclass

my_circle.draw()  # Calls the overridden draw method in the Circle subclass

In this example:

  • is the superclass with an method to initialize the color attribute and a method.
  • is the subclass that inherits from . It has its own method to initialize the radius attribute and overrides the method.

When you run this code, you will get the following output:

Drawing a circle with color Blue and radius 5

This demonstrates a simple example of single inheritance in Python, where inherits from the superclass and customizes its behavior through its own constructor and method overrides.

2. Multiple Inheritance

Multiple inheritance in Python occurs when a class inherits from more than one superclass. Here's an example:

# Define a superclass 'Person'

class Person:

    def __init__(self, name, age):

        self.name = name

        self.age = age

    def display_info(self):

        print(f"Name: {self.name}, Age: {self.age}")

# Define another superclass 'Employee'

class Employee:

    def __init__(self, employee_id, salary):

        self.employee_id = employee_id

        self.salary = salary

    def display_info(self):

        print(f"Employee ID: {self.employee_id}, Salary: ${self.salary}")

# Define a subclass 'Manager' that inherits from both 'Person' and 'Employee'

class Manager(Person, Employee):

    def __init__(self, name, age, employee_id, salary, department):

        # Call constructors of both superclasses using super()

        super().__init__(name, age)

        super(Employee, self).__init__(employee_id, salary)

        self.department = department

    def display_info(self):

        # Override display_info method to provide additional information

        super(Person, self).display_info()

        super(Employee, self).display_info()

        print(f"Department: {self.department}")

# Create an instance of 'Manager'

manager_instance = Manager("John Doe", 35, "M123", 75000, "IT")

# Access methods from all superclasses and the subclass

manager_instance.display_info()

In this example:

  •   and are two separate superclasses with their own attributes and methods.
  • is a subclass that inherits from both and using multiple inheritance.
  • The class overrides the method to provide additional information.

When you run this code, you will get the following output:

Name: John Doe, Age: 35

Employee ID: M123, Salary: $75000

Department: IT

This demonstrates multiple inheritance where the class inherits from both and , combining attributes and methods from both superclasses.

 3. Multilevel Inheritance

In multilevel inheritance, a subclass inherits from another subclass, forming a chain of inheritance. Here's an example in Python:

# Define a superclass 'Vehicle'

class Vehicle:

    def __init__(self, brand):

        self.brand = brand

    def display_info(self):

        print(f"This is a {self.brand} vehicle.")

# Define a subclass 'Car' that inherits from 'Vehicle'

class Car(Vehicle):

    def __init__(self, brand, model):

        super().__init__(brand)

        self.model = model

    def display_info(self):

        print(f"This is a {self.brand} car, model {self.model}.")

# Define another subclass 'SportsCar' that inherits from 'Car'

class SportsCar(Car):

    def __init__(self, brand, model, top_speed):

        super().__init__(brand, model)

        self.top_speed = top_speed

    def display_info(self):

        print(f"This is a {self.brand} sports car, model {self.model}, with a top speed of {self.top _speed} mph.")

# Create an instance of 'SportsCar'

my_sports_car = SportsCar("Ferrari", "488 GTB", 205)

# Access methods from all levels of the inheritance chain

my_sports_car.display_info()

In this example:

  • is the superclass with an method and a method.
  • is a subclass of that adds a attribute.
  • is a subclass of that adds a attribute.

When you run this code, you will get the following output:

This is a Ferrari sports car, model 488 GTB, with a top speed of 205 mph.

This demonstrates multilevel inheritance where inherits from , and inherits from . Each subclass can extend and override methods from its parent class, creating a chain of inheritance.

4. Hierarchical Inheritance

In hierarchical inheritance, multiple subclasses inherit from a single superclass. Here's a simple Python example:

# Define a superclass 'Animal'

class Animal:

    def __init__(self, species):

        self.species = species

    def make_sound(self):

        print("Generic animal sound")

# Create two subclasses 'Dog' and 'Cat' inheriting from 'Animal'

class Dog(Animal):

    def __init__(self, species, breed):

        super().__init__(species)

        self.breed = breed

    def make_sound(self):

        print("Woof!")

class Cat(Animal):

    def __init__(self, species, color):

        super().__init__(species)

        self.color = color

    def make_sound(self):

        print("Meow!")

# Create instances of 'Dog' and 'Cat'

my_dog = Dog("Canine", "Golden Retriever")

my_cat = Cat("Feline", "Tabby")

# Display information using methods from the superclass and subclasses

print(f"My {my_dog.species} is a {my_dog.breed}.")

my_dog.make_sound()

print(f"My {my_cat.species} is {my_cat.color}.")

my_cat.make_sound()

In this example:

  • is the superclass with attributes like and a method .
  • and are subclasses of , inheriting the attributes and methods.
  • Each subclass has its own method to initialize additional attributes and overrides the method to provide more specific information.

When you run this code, you will get the following output:

My Canine is a Golden Retriever.

Woof!

My Feline is Tabby.

Meow!

This showcases hierarchical inheritance, where both and share common features from the superclass while having their own distinct characteristics.

Method Resolution Order (MRO)

Python uses C3 linearization to determine the order in which classes are inherited. This order is known as the Method Resolution Order (MRO).

   class A:

       pass

   class B(A):

       pass

   class C(A):

       pass

   class D(B, C):

       pass

   # MRO for class D: D, B, C, A

Practical Examples

  • Using Super(): The super() in Python is a built-in function used to call a method from the superclass within a subclass. It provides a way for a subclass to invoke the methods of its superclass. Here's an example illustrating the use of  in Python. 

# Define a superclass 'Person'

class Person:

    def __init__(self, name, age):

        self.name = name

        self.age = age

    def display_info(self):

        print(f"Name: {self.name}, Age: {self.age}")

# Define a subclass 'Employee' that inherits from 'Person'

class Employee(Person):

    def __init__(self, name, age, employee_id, salary):

        # Call the constructor of the superclass using super()

        super().__init__(name, age)

        self.employee_id = employee_id

        self.salary = salary

    def display_info(self):

        # Call the method of the superclass using super()

        super().display_info()

        print(f"Employee ID: {self.employee_id}, Salary: ${self.salary}")

# Create an instance of 'Employee'

employee_instance = Employee("John Doe", 30, "E123", 60000)

# Access methods from both the superclass and the subclass

employee_instance.display_info()

In this example:

  • is the superclass with attributes and and a method .
  • is the subclass that inherits from . The method of is called using in the constructor, and the method of is called similarly in the overridden method of .

When you run this code, you will get the following output:

Name: John Doe, Age: 30
Employee ID: E123, Salary: $60000

This demonstrates how is used to call methods and constructors from the superclass within the subclass.

  • Creating Specialized Classes: Inheritance allows you to create specialized classes based on existing ones, promoting code reuse. Let's explore another example of creating specialized classes in Python, this time focusing on vehicles:

# Define a superclass 'Vehicle'

class Vehicle:

    def __init__(self, brand, model):

        self.brand = brand

        self.model = model

    def display_info(self):

        print(f"{self.brand} {self.model}")

# Define a subclass 'Car' that specializes 'Vehicle'

class Car(Vehicle):

    def __init__(self, brand, model, num_doors):

        super().__init__(brand, model)

        self.num_doors = num_doors

    def display_info(self):

        super().display_info()

        print(f"Number of doors: {self.num_doors}")

# Define another subclass 'Motorcycle' that specializes 'Vehicle'

class Motorcycle(Vehicle):

    def __init__(self, brand, model, has_sidecar):

        super().__init__(brand, model)

        self.has_sidecar = has_sidecar

    def display_info(self):

        super().display_info()

        if self.has_sidecar:

            print("Has a sidecar")

        else:

            print("No sidecar")

# Create instances of 'Car' and 'Motorcycle'

my_car = Car("Toyota", "Camry", 4)

my_motorcycle = Motorcycle("Harley-Davidson", "Sportster", True)

# Access specialized methods for each instance

my_car.display_info()

#         Number of doors: 4

my_motorcycle.display_info()

In this example:

  • is the superclass with attributes and and a method .
  • and are subclasses that specialize , providing additional attributes ( and ) and overriding the method.

When you run this code, you will get the following output:

Toyota Camry

Number of doors: 4

Harley-Davidson Sportster

Has a sidecar

When you create instances of and and call the method on each instance, you get specific information based on the specialized implementation in each subclass. This showcases the concept of creating specialized classes in Python through inheritance.

  • Overriding Methods: Overriding methods in Python allows a subclass to provide a specific implementation for a method that is already defined in its superclass. Here's an example illustrating method overriding:
# Define a superclass Vehicle'

class Vehicle:

    def start_engine(self):

        print("Generic engine start")

# Define a subclass 'Car' that overrides the 'start_engine' method

class Car(Vehicle):

    def start_engine(self):

        print("Car engine started")

# Define another subclass 'Motorcycle' that also overrides 'start_engine'

class Motorcycle(Vehicle):

    def start_engine(self):

        print("Motorcycle engine started")

# Create instances of 'Car' and 'Motorcycle'

my_car = Car()

my_motorcycle = Motorcycle()

# Call the overridden method for each instance

my_car.start_engine()       

my_motorcycle.start_engine() 

In this example:

  • is the superclass with a method called .
  • Both and are subclasses that override the method, providing their own specific implementations.

When you create instances of and and call the method on each instance, you get the specific output defined in their respective subclasses.

When you run this code, you will get the following output:

Car engine started
Motorcycle engine started

Method overriding is a fundamental concept in object-oriented programming that allows a subclass to customize or extend the behavior inherited from its superclass.

Advantages of Python Inheritance

  • Code Reusability: Inheritance promotes the reuse of code, reducing redundancy and enhancing maintainability.
  • Organized Code Structure: It provides a clear and organized structure to the code by grouping related functionalities in classes. 
  • Easier Maintenance: When changes are needed, modifying the superclass automatically reflects in all its subclasses, streamlining maintenance.

Best Practices for Python Inheritance

  • Follow the "is-a" Relationship: Use inheritance when there is a clear "is-a" relationship between the subclass and superclass. 
  • Avoid Deep Inheritance Hierarchies: Keep inheritance hierarchies simple to avoid confusion and potential issues. 
  • Use Composition When Needed: In some cases, composition (using objects of other classes) might be a better choice than inheritance.

Conclusion

Python Inheritance is a powerful mechanism that contributes to the elegance and efficiency of object-oriented programming. By grasping its syntax and exploring various examples, you can leverage this feature to build scalable, maintainable, and organized code. Incorporate these principles into your coding practices, and watch your Python projects flourish with clarity and reusability.