This article is part of in the series
Published: Friday 16th May 2025

python inheritance

In the world of object-oriented programming, inheritance stands as a fundamental pillar that enables code reuse, promotes logical organization, and supports elegant software design. Python, with its clean syntax and flexible approach to OOP, offers a particularly accessible yet powerful implementation of inheritance. This article explores the concept of inheritance in Python, covering basic principles, advanced techniques, common patterns, and best practices to help you write more maintainable and efficient code.

Understanding the Basics of Inheritance in Python

At its core, inheritance allows a class (called a subclass or derived class) to inherit attributes and methods from another class (called a superclass or base class). This creates a parent-child relationship between classes, establishing a hierarchy that models "is-a" relationships.

Basic Syntax and Implementation

In Python, creating a subclass is straightforward:

class Animal: def __init__(self, name): self.name = name def speak(self): pass # This will be implemented by subclasses class Dog(Animal): def speak(self): return f"{self.name} says Woof!" class Cat(Animal): def speak(self): return f"{self.name} says Meow!" # Creating instances dog = Dog("Rex") cat = Cat("Whiskers") print(dog.speak()) # Output: Rex says Woof! print(cat.speak()) # Output: Whiskers says Meow! 

In this example, Dog and Cat inherit from Animal, gaining access to its name attribute while providing their own implementations of the speak method.

The super() Function

When a subclass needs to extend (rather than completely replace) the functionality of its parent class, the super() function comes into play:

class Animal: def __init__(self, name, age): self.name = name self.age = age def info(self): return f"{self.name} is {self.age} years old" class Dog(Animal): def __init__(self, name, age, breed): super().__init__(name, age) # Call parent's __init__ self.breed = breed def info(self): base_info = super().info() # Call parent's info method return f"{base_info} and is a {self.breed}" dog = Dog("Rex", 3, "German Shepherd") print(dog.info()) # Output: Rex is 3 years old and is a German Shepherd 

The super() function provides a reference to the parent class, allowing you to call its methods. This is particularly useful in constructors and when overriding methods.

Types of Inheritance in Python

Python supports various inheritance patterns to model different kinds of relationships:

Single Inheritance

The simplest form, where a subclass inherits from a single superclass, as shown in the previous examples.

Multiple Inheritance

One of Python's powerful features is its support for multiple inheritance, where a class can inherit from multiple parent classes:

class Flying: def fly(self): return "I can fly!" class Swimming: def swim(self): return "I can swim!" class Duck(Flying, Swimming): def speak(self): return "Quack!" duck = Duck() print(duck.fly()) # Output: I can fly! print(duck.swim()) # Output: I can swim! print(duck.speak()) # Output: Quack! 

Multiple inheritance allows a class to combine behaviors from different sources, though it should be used judiciously to avoid complexity.

Multilevel Inheritance

In multilevel inheritance, a class inherits from a subclass, creating a "grandparent" relationship:

class Animal: def breathe(self): return "I breathe oxygen" class Mammal(Animal): def feed_young(self): return "I feed my young with milk" class Dog(Mammal): def bark(self): return "Woof!" dog = Dog() print(dog.breathe()) # From Animal print(dog.feed_young()) # From Mammal print(dog.bark()) # From Dog 

This creates deeper hierarchies that represent increasingly specific categories.

Hierarchical Inheritance

When multiple subclasses inherit from the same parent class:

class Vehicle: def __init__(self, brand): self.brand = brand class Car(Vehicle): def type(self): return "I am a car" class Motorcycle(Vehicle): def type(self): return "I am a motorcycle" # Both inherit from Vehicle car = Car("Toyota") motorcycle = Motorcycle("Harley-Davidson") 

Hybrid Inheritance

A combination of multiple inheritance types, often seen in complex systems:

class A: def method_a(self): return "Method A" class B(A): def method_b(self): return "Method B" class C(A): def method_c(self): return "Method C" class D(B, C): def method_d(self): return "Method D" # D inherits from B and C, which both inherit from A 

Method Resolution Order (MRO)

When dealing with multiple inheritance, understanding how Python resolves method calls becomes crucial. Python uses the C3 linearization algorithm to determine the Method Resolution Order (MRO).

class A: def who_am_i(self): return "I am A" class B(A): def who_am_i(self): return "I am B" class C(A): def who_am_i(self): return "I am C" class D(B, C): pass d = D() print(d.who_am_i()) # Output: I am B print(D.__mro__) # Shows the resolution order 

The MRO determines which method gets called when the same method exists in multiple parent classes. You can inspect the MRO using the __mro__ attribute or the mro() method.

Advanced Inheritance Techniques

Abstract Base Classes

Abstract base classes (ABCs) define interfaces that derived classes must implement:

from abc import ABC, abstractmethod class Shape(ABC): @abstractmethod def area(self): pass @abstractmethod def perimeter(self): pass class Rectangle(Shape): def __init__(self, width, height): self.width = width self.height = height def area(self): return self.width * self.height def perimeter(self): return 2 * (self.width + self.height) # This would raise an error if area or perimeter weren't implemented rect = Rectangle(5, 10) 

ABCs ensure that subclasses provide implementations for required methods, helping to enforce consistent interfaces.

Mixins

Mixins are classes designed to provide additional functionality to other classes through multiple inheritance:

class JSONSerializableMixin: def to_json(self): import json return json.dumps(self.__dict__) class Person(JSONSerializableMixin): def __init__(self, name, age): self.name = name self.age = age person = Person("Alice", 30) print(person.to_json()) # Output: {"name": "Alice", "age": 30} 

They also  provide a way to "mix in" functionality without creating deep inheritance hierarchies.

Properties and Inheritance

Properties allow controlled access to attributes and work well with inheritance:

class Person: def __init__(self, name): self._name = name @property def name(self): return self._name @name.setter def name(self, value): if not isinstance(value, str): raise TypeError("Name must be a string") self._name = value class Employee(Person): def __init__(self, name, employee_id): super().__init__(name) self.employee_id = employee_id # Employee inherits the name property with its validation 

Best Practices and Common Patterns

Favor Composition Over Inheritance

While inheritance is powerful, composition (creating relationships through object references) often provides more flexibility:

# Instead of inheritance: class Car(Vehicle): pass # Consider composition: class Car: def __init__(self): self.engine = Engine() self.wheels = [Wheel() for _ in range(4)] 

This approach can lead to more maintainable code, especially in complex systems.

Keep Inheritance Hierarchies Shallow

Deep inheritance hierarchies can become difficult to understand and maintain. Try to keep your hierarchies no more than two or three levels deep when possible.

Use Inheritance for "Is-A" Relationships

Inheritance should model an "is-a" relationship (a Dog is an Animal), while composition should model "has-a" relationships (a Car has an Engine).

Design for Inheritance or Prohibit It

Classes should either be designed for inheritance (with documented extension points) or explicitly prevent it. In Python 3.8+, you can use:

class FinalClass(final=True): """This class cannot be subclassed.""" pass 

For earlier Python versions, create a metaclass or document the intent clearly.

Common Inheritance Patterns in Python

The Template Method Pattern

Define the skeleton of an algorithm in a method, deferring some steps to subclasses:

class DataProcessor: def process(self, data): clean_data = self.clean(data) processed_data = self.transform(clean_data) return self.format(processed_data) def clean(self, data): # Default implementation return data def transform(self, data): # Subclasses must implement this raise NotImplementedError def format(self, data): # Default implementation return data class CSVProcessor(DataProcessor): def transform(self, data): # Specific implementation for CSV return [row.split(',') for row in data] 

The Strategy Pattern

While often implemented through composition, inheritance can also be used:

class SortingAlgorithm: def sort(self, data): pass class QuickSort(SortingAlgorithm): def sort(self, data): # Implement quicksort return sorted(data) # Simplified for example class MergeSort(SortingAlgorithm): def sort(self, data): # Implement mergesort return sorted(data) # Simplified for example 

Common Pitfalls and How to Avoid Them

Diamond Problem

The diamond problem occurs when a class inherits from two classes that both inherit from a common base class:

class A: def method(self): return "A" class B(A): def method(self): return "B" class C(A): def method(self): return "C" class D(B, C): pass # Which method() does D inherit? 

Python's MRO resolves this, but it's best to avoid such complex inheritance structures when possible.

Overusing Inheritance

Inheritance creates tight coupling between classes. Consider whether interfaces, composition, or simple functions might serve your needs better.

Ignoring the Liskov Substitution Principle

Subclasses should be substitutable for their base classes without altering the correctness of the program:

# Problematic class Rectangle: def __init__(self, width, height): self.width = width self.height = height def set_width(self, width): self.width = width def set_height(self, height): self.height = height class Square(Rectangle): def set_width(self, width): self.width = width self.height = width # Violates expectations for a Rectangle def set_height(self, height): self.height = height self.width = height # Violates expectations for a Rectangle 

Summary

Python's implementation of inheritance provides a powerful tool for modeling relationships between classes, enabling code reuse, and creating well-structured object hierarchies. By understanding the different types of inheritance, advanced techniques like abstract base classes and mixins, and common patterns and pitfalls, you can leverage inheritance effectively in your Python projects.

Similar Articles

https://www.w3schools.com/python/python_inheritance.asp

https://realpython.com/inheritance-composition-python/

More from Python Central

https://www.pythoncentral.io/series/python-classes-tutorial/

Python DefaultDict: Efficient Dictionary Handling