Python tutorials > Object-Oriented Programming (OOP) > Inheritance > How to implement inheritance?

How to implement inheritance?

Inheritance is a fundamental concept in object-oriented programming (OOP) that allows you to create new classes (child classes or subclasses) based on existing classes (parent classes or superclasses). The child class inherits attributes and methods from the parent class, enabling code reuse and promoting a hierarchical structure. This tutorial will guide you through implementing inheritance in Python, providing code examples and explanations.

Basic Inheritance: Creating a Subclass

This code demonstrates basic inheritance. The Animal class is the parent class. The Dog and Cat classes inherit from the Animal class. The Dog and Cat classes override the speak method to provide their specific sounds. The __init__ method is inherited, and the name attribute is initialized in the parent class.

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

    def speak(self):
        print("Generic animal sound")

class Dog(Animal):
    def speak(self):
        print("Woof!")

my_dog = Dog("Buddy")
print(my_dog.name)
my_dog.speak()

class Cat(Animal):
    def speak(self):
        print("Meow!")

my_cat = Cat("Whiskers")
print(my_cat.name)
my_cat.speak()

Concepts Behind the Snippet

Inheritance: Allows a class (child class) to inherit attributes and methods from another class (parent class).
Parent Class (Superclass): The class being inherited from.
Child Class (Subclass): The class that inherits from another class.
Method Overriding: Redefining a method in the child class that is already defined in the parent class.
super(): Used to call methods and access attributes from the parent class within the child class, especially useful for initializing parent class attributes or extending parent class functionality. It ensures that the parent class's initialization logic is executed when creating an instance of the child class.

Using super() to Initialize Parent Class Attributes

This code shows how to use super() to call the parent class's constructor and initialize its attributes. The Dog class's __init__ method calls super().__init__(name, "Dog") to initialize the name and species attributes inherited from the Animal class. The Dog class then initializes its own breed attribute. This ensures that the parent class's initialization logic is executed.

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

    def speak(self):
        print("Generic animal sound")

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name, species="Dog") #Call Animal constructor
        self.breed = breed

    def speak(self):
        print("Woof!")

my_dog = Dog("Buddy", "Golden Retriever")
print(my_dog.name)
print(my_dog.species)
print(my_dog.breed)
my_dog.speak()

Multiple Inheritance

This code demonstrates multiple inheritance, where a class inherits from multiple parent classes. The Amphibian class inherits from both the Swimmer and Walker classes, gaining both the swim and walk methods.

class Swimmer:
    def swim(self):
        print("Swimming...")

class Walker:
    def walk(self):
        print("Walking...")

class Amphibian(Swimmer, Walker):
    pass

frog = Amphibian()
frog.swim()
frog.walk()

Real-Life Use Case: Different Types of Employees

Consider a company with different types of employees like managers and developers. You can define a base class Employee with common attributes like name, employee_id, and methods like get_salary. Then, you can create subclasses Manager and Developer that inherit from Employee and add specific attributes and methods related to their roles, like department for Manager and programming_language for Developer. This promotes code reuse and a clear class hierarchy.

Best Practices

  • Keep inheritance hierarchies shallow: Deep inheritance hierarchies can become difficult to understand and maintain. Try to keep the hierarchy depth reasonable.
  • Use composition over inheritance when appropriate: If a class needs functionality from another class but doesn't necessarily fit the 'is-a' relationship, consider using composition instead of inheritance.
  • Be mindful of the Liskov Substitution Principle: Subclasses should be substitutable for their parent classes without altering the correctness of the program.
  • Document your inheritance structure: Clearly document the relationships between classes in your inheritance hierarchy to improve code maintainability.

Interview Tip

Be prepared to explain the 'is-a' relationship when discussing inheritance. For example, a Dog 'is a' Animal. Also, be ready to discuss the benefits of inheritance, such as code reuse and polymorphism, and the potential drawbacks, like increased complexity if not used carefully. Understand the difference between inheritance and composition.

When to use Inheritance

Use inheritance when you have a clear 'is-a' relationship between classes and you want to reuse code and create a hierarchical structure. Inheritance promotes code reuse, reduces redundancy, and enables polymorphism. Good candidates are scenarios where subclasses share common attributes and methods with a parent class but also have their own specific functionalities.

Memory Footprint

Inheritance generally adds a small overhead to the memory footprint. Each instance of a subclass will store the attributes defined in the subclass as well as the attributes inherited from the parent class(es). The method resolution order (MRO) also adds a small overhead. However, the benefits of code reuse and organization often outweigh this slight increase in memory usage.

Alternatives to Inheritance

  • Composition: Instead of inheriting from a class, a class can contain an instance of another class as an attribute. This allows you to reuse functionality without creating an 'is-a' relationship.
  • Mixins: Mixins are small classes that provide specific functionality that can be mixed into other classes using multiple inheritance. This can be a cleaner way to add functionality than deep inheritance hierarchies.
  • Interfaces (Abstract Base Classes): Abstract base classes define a set of methods that must be implemented by any concrete subclass. This enforces a certain structure and ensures that all subclasses provide the required functionality.

Pros of Inheritance

  • Code Reuse: Reduces code duplication by allowing subclasses to inherit attributes and methods from parent classes.
  • Organization: Creates a hierarchical structure that makes code easier to understand and maintain.
  • Polymorphism: Allows objects of different classes to be treated as objects of a common type.

Cons of Inheritance

  • Increased Complexity: Deep inheritance hierarchies can become difficult to understand and maintain.
  • Tight Coupling: Subclasses are tightly coupled to their parent classes, making it difficult to modify the parent class without affecting the subclasses.
  • Fragile Base Class Problem: Changes to the parent class can unintentionally break subclasses.

FAQ

  • What is the difference between inheritance and composition?

    Inheritance establishes an 'is-a' relationship between classes (e.g., a Dog 'is a' Animal), allowing a subclass to inherit attributes and methods from a parent class. Composition establishes a 'has-a' relationship (e.g., a Car 'has a' Engine), where a class contains an instance of another class as an attribute. Composition promotes looser coupling and more flexibility than inheritance.

  • How does method overriding work in inheritance?

    Method overriding allows a subclass to provide a specific implementation for a method that is already defined in its parent class. When a method is called on an instance of the subclass, the subclass's implementation of the method is executed instead of the parent class's implementation.

  • What is the purpose of the super() function?

    The super() function is used to call methods and access attributes from the parent class within the child class. It is particularly useful for initializing parent class attributes in the child class's constructor or for extending the functionality of a parent class method.