Python Inheritance: Best Practices for Reusable Code
Imagine you are building a software system with multiple user roles like students, teachers, and administrators. These roles share common attributes like name and ID, but they also require functionalities that are unique to them. Instead of duplicating code, inheritance allows you to define shared behavior in a parent class and extend it in specialized child classes.
In this article, we will explore Python inheritance, covering both basic and advanced concepts such as method overriding and the super()
function, which is a built-in function that returns a temporary object of the superclass, so you can access its methods without explicitly naming the parent class. Don't worry if that doesn't make sense yet because we will learn all about this below.
Learn Python From Scratch
Basics of Python Inheritance
Inheritance is one of the foundational pillars of object-oriented programming (OOP) that allows one class (called the child class) to derive attributes and methods from another class (called the parent class). This feature is central to code reuse and simplifies maintenance, making it easier to build scalable and efficient programs.
Defining parent and child classes
Before going further, let's explore the relationship between parent and child classes.
Parent class
Let’s start with the parent class. A parent class is the base class from which child classes derive. It encapsulates shared attributes and methods.
Using Python, here is how we define a parent class:
class ParentClass:
def __init__(self, attributes):
# Initialize attributes
pass
def method(self):
# Define behavior
pass
Child class
A child class inherits attributes and methods from the parent class. This allows it to use the functionality defined in the parent class. The following code shows how a child class inherits attributes and methods from a parent class:
class ChildClass(ParentClass):
def additional_method(self):
# Define new behavior
pass
This simple syntax allows the child class to utilize and extend the functionality defined in the parent class.
Creating a parent class and a child class
Let’s create a practical example with a Person
class as the parent and a Student
class as the child.
Creating the parent class
The Person
class contains shared attributes and a method to display information:
# Defining the Parent Class
class Person:
def __init__(self, name, id):
self.name = name
self.id = id
def display_info(self):
return f"Name: {self.name}, ID: {self.id}"
Creating the child class
The Student
class inherits from Person
and adds a new method study
.
# Defining the Child Class
class Student(Person):
def study(self):
return f"{self.name} is studying."
Let’s test the parent and child classes.
# Creating and Testing Instances
student = Student("Samuel", 102)
print(student.display_info())
print(student.study())
Name: Samuel, ID: 102
Samuel is studying.
Here’s what’s happening:
-
The
Student
class uses the__init__
method fromPerson
to initializename
andid
. -
The
study
method is unique to theStudent
class, extending its functionality. -
The
display_info
method is inherited directly fromPerson
.
Types of Inheritance in Python
Inheritance in Python allows classes to inherit attributes and behaviors from other classes, promoting code reuse and clean design, as we talked about earlier. In this section, we can talk about the different types of Python inheritance, which includes single, multiple, hierarchical, and hybrid inheritance as separate categories.
Single inheritance
Single inheritance occurs when a child class inherits from a single parent class, allowing it to extend the functionality of the parent. This is useful when an object type shares common properties with a broader category but also requires additional attributes or behavior.
The example I started to work through earlier was single inheritance, but let's now look a bit more closely: In a school management system, all individuals, including students, teachers, and staff, share some common details like name
and ID
. However, students also have academic records such as grades and enrolled courses. Using single inheritance, we can create a Person
class for shared attributes and extend it with a Student
class for academic details.
Single inheritance. Image by Author
Here’s a good example of the above scenario:
class Person:
"""Represents a general person with basic details."""
def __init__(self, name, id):
self.name = name
self.id = id
def get_details(self):
return f"Name: {self.name}, ID: {self.id}"
class Student(Person):
"""Represents a student, extending the Person class to include academic details."""
def __init__(self, name, id, grade, courses):
super().__init__(name, id)
self.grade = grade
self.courses = courses
def get_details(self):
return f"Name: {self.name}, ID: {self.id}, Grade: {self.grade}, Courses: {', '.join(self.courses)}"
# Example usage in a school system
student = Student("Samuel", 5678, "B+", ["Math", "Physics", "Computer Science"])
print(student.get_details())
Name: Samuel, ID: 5678, Grade: B+, Courses: Math, Physics, Computer Science
The Student
class inherits the get_details()
method from Person
but extends it to include grade
and courses
. This is a good example of how single inheritance promotes what is known as modular code.
Multiple inheritance
Multiple inheritance, like a family tree, in a way, allows a child class to inherit from more than one parent class, combining attributes and behaviors from each. This can lead to potential conflicts, which Python resolves using method resolution order (MRO).
Multiple inheritance. Image by Author
Take a look:
class Person:
def get_details(self):
return "Details of a person."
class Athlete:
def get_skill(self):
return "Athletic skills."
class Student(Person, Athlete):
pass
# Example usage
student = Student()
print(student.get_details())
print(student.get_skill())
Details of a person.
Athletic skills.
We see that the Student
class inherited attributes and methods from both Person
and Athlete
. Without any additional effort, the Student
class has access to the get_details()
method from the Person
parent class and the get_skill()
method from the Athlete
parent class. We are effectively combining functionality from multiple sources.
However, inheriting from multiple classes can lead to conflicts. What if both parent classes define a method or attribute with the same name? I mentioned something about method resolution order earlier but let me know say a little something more about it. Method resolution order determines the order in which classes are searched for methods and attributes. The MRO follows a depth-first, left-to-right approach.
You can view the MRO of a class using the __mro__
attribute or the mro()
method:
print(Student.__mro__)
(<class '__main__.Student'>, <class '__main__.Person'>, <class '__main__.Athlete'>, <class 'object'>)
Multilevel, hierarchical, and hybrid inheritance
Python also supports more complex inheritance structures. I'll show these more complex ideas using the same example.
Multilevel inheritance
Multilevel inheritance happens when a child class inherits from another child class, and that child class inherits from a parent class. This creates a chain of inheritance.
Multilevel inheritance. Image by Author
Here’s a good example:
class Person:
def __init__(self, name, id):
self.name = name
self.id = id
def get_details(self):
return f"Name: {self.name}, ID: {self.id}"
class Student(Person):
def __init__(self, name, id, grade):
super().__init__(name, id)
self.grade = grade
def get_details(self):
return f"Name: {self.name}, ID: {self.id}, Grade: {self.grade}"
class GraduateStudent(Student):
def __init__(self, name, id, grade, thesis_title):
super().__init__(name, id, grade)
self.thesis_title = thesis_title
def get_details(self):
return f"Name: {self.name}, ID: {self.id}, Grade: {self.grade}, Thesis: {self.thesis_title}"
# Example usage
grad_student = GraduateStudent("Charlie", 91011, "A", "AI in Healthcare")
print(grad_student.get_details())
Name: Charlie, ID: 91011, Grade: A, Thesis: AI in Healthcare
Here, each class in the chain adds something new: Person
manages names and IDs, Student
includes grades, and GraduateStudent
introduces a thesis. Thanks to super().__init__()
, we reuse the initialization logic without duplicating code. It’s efficient, neat, and ensures every level of the “inheritance ladder”, as I think of it, works.
Hierarchical inheritance
In hierarchical inheritance, multiple child classes inherit from a single parent class, allowing for shared behavior across subclasses with unique attributes.
Hierarchical inheritance. Image by Author
Let’s look at a good example together:
class Person:
def __init__(self, name, id):
self.name = name
self.id = id
def get_details(self):
return f"Name: {self.name}, ID: {self.id}"
class Student(Person):
def __init__(self, name, id, grade):
super().__init__(name, id)
self.grade = grade
def get_details(self):
return f"Name: {self.name}, ID: {self.id}, Grade: {self.grade}"
class Teacher(Person):
def __init__(self, name, id, subject):
super().__init__(name, id)
self.subject = subject
def get_details(self):
return f"Name: {self.name}, ID: {self.id}, Subject: {self.subject}"
# Example usage
student = Student("Samuel", 5678, "B+")
teacher = Teacher("Dr. Smith", 1234, "Math")
print(student.get_details())
print(teacher.get_details())
Name: Samuel, ID: 5678, Grade: B+
Dr. Smith, ID: 1234, Subject: Math
Here, the Person
class serves as the foundation, offering common attributes and methods (name
, id
, and get_details
). The Student
and Teacher
classes then extend this functionality by adding their unique properties (grade
and subject
) and customizing the get_details
method to reflect their specific contexts.
With this approach, shared functionality stays in one place (the Person
class), while specialized behavior is neatly encapsulated in the subclasses.
Hybrid inheritance
Hybrid inheritance combines multiple inheritance types, such as multilevel or multiple inheritance, to model more complex relationships.
Hybrid inheritance. Image by Author
Let’s look at an example that shows the complexity of hybrid inheritance.
# Base class
class Person:
def __init__(self, name, id):
self.name = name
self.id = id
def get_details(self):
return f"Name: {self.name}, ID: {self.id}"
# Intermediate class inheriting from the base class
class Employee(Person):
def __init__(self, name, id, position):
super().__init__(name, id)
self.position = position
def get_position(self):
return f"Position: {self.position}"
# Another independent base class
class Athlete:
def __init__(self, sport):
self.sport = sport
def get_sport(self):
return f"Sport: {self.sport}"
# Derived class combining Employee and Athlete
class Student(Employee, Athlete):
def __init__(self, name, id, position, grade, sport):
Employee.__init__(self, name, id, position)
Athlete.__init__(self, sport)
self.grade = grade
def get_grade(self):
return f"Grade: {self.grade}"
# Example usage
student = Student("Samuel", 1234, "Intern", "A", "Soccer")
print(student.get_details()) # From Person
print(student.get_position()) # From Employee
print(student.get_grade()) # From Student
print(student.get_sport()) # From Athlete
Name: Samuel, ID: 1234
Position: Intern
Grade: A
Sport: Soccer
In this example, the Student
class demonstrates hybrid inheritance by inheriting attributes and methods from both Employee
(which itself inherits from Person
) and Athlete
. This combines hierarchical inheritance (where Employee
inherits from Person
) and multiple inheritance (where Student
inherits from both Employee
and Athlete
).
Benefits of Inheritance in Python
Now, it's time to see the strengths and weaknesses:
Benefits of inheritance
-
Reusability: With inheritance you can write code once in the parent class and reuse it in the child classes. Using the example, both
FullTimeEmployee
andContractor
can inherit aget_details()
method from theEmployee
parent class. -
Simplicity: Inheritance models relationships clearly. A good example is the
FullTimeEmployee
class which “is-a” type of theEmployee
parent class. -
Scalability: It also add new features or child classes without affecting existing code. For example, we can easily add a new
Intern
class as a child class.
Potential limitations of inheritance
-
Complexity: This won't be surprising, but too many levels of inheritance can make the code hard to follow. For example, if an
Employee
has too many child classes likeManager
,Engineer
,Intern
, etc., it may become confusing. -
Dependency: Changes to a parent class can unintentionally affect all subclasses. If you modify
Employee
for example, it might breakFullTimeEmployee
orContractor
. -
Misuse: Using inheritance when it is not the best fit can complicate designs. You would not want to create a solution where
Car
inherits fromBoat
just to reusemove()
. The relationship doesn’t make sense.
Advanced Python Inheritance Techniques
Now that we have explored the basics of inheritance, let’s look at some advanced techniques. These techniques, like method overriding, super()
, abstract base classes, and polymorphism, enhance code flexibility and allow for more sophisticated design patterns.
Overriding methods in python
Method overriding allows a child class to provide a specific implementation for a method already defined in its parent class. This is useful when the inherited behavior doesn’t fully meet the requirements of the child class.
class Person:
def __init__(self, name, id):
self.name = name
self.id = id
def get_details(self):
return f"Name: {self.name}, ID: {self.id}"
class Student(Person):
def __init__(self, name, id, grade):
super().__init__(name, id)
self.grade = grade
# Overriding the get_details method
def get_details(self):
return f"Name: {self.name}, ID: {self.id}, Grade: {self.grade}"
# Example usage
student = Student("Samuel", 1234, "A")
print(student.get_details())
Name: Samuel, ID: 1234, Grade: A
Here, the Student
class overrides the get_details()
method from the Person
class to give its own specific implementations. This allows the child class to have its own behavior while still following the same method name.
So why do we override? We override because we want to customize inherited behavior and also because we want to tailor the functionality of a parent method to a child class’s unique requirements.
Using super() for parent initialization
The super()
function is used to call methods in the parent class from the child class. This is particularly useful when you want to extend or modify the functionality of a parent class method, such as the __Init__()
constructor method.
So why do we use the super()
function? We use the super function because we want to call and initialize the parent class’s constructor and also because we want to avoid explicitly naming the parent class. This is helpful, especially in cases of multiple inheritance.
class Person:
def __init__(self, name, id):
self.name = name
self.id = id
class Student(Person):
def __init__(self, name, id, grade):
# Using super() to initialize the parent class
super().__init__(name, id)
self.grade = grade
# Example usage
student = Student("Samuel", 5678, "B+")
print(student.name)
print(student.id)
print(student.grade)
Samuel
5678
B+
Here, the Student
class uses super().__init__(name, id)
to call the __init__
method of the Person
parent class, so it does not need to repeat code to initialize the name
and id
attributes. The child class then introduces a grade
attribute, which is specific to the Student
class.
Abstract base classes (ABCs)
An abstract base class (ABC) is a class that cannot be directly used to create objects. It is meant to define a common set of methods that other classes should implement. So ABCs are useful when you want to ensure that certain methods are always present in the child classes.
from abc import ABC, abstractmethod
class Person(ABC):
@abstractmethod
def get_details(self):
pass
class Student(Person):
def __init__(self, name, id, grade):
self.name = name
self.id = id
self.grade = grade
def get_details(self):
return f"Name: {self.name}, ID: {self.id}, Grade: {self.grade}"
# Example usage
student = Student("Hamilton", 7890, "A-")
print(student.get_details())
Name: Hamilton, ID: 7890, Grade: A-
The Person
class here is an abstract class that requires any child class to implement the get_details()
method. This method is being later implemented by the child class, Student
.
Polymorphism
Polymorphism means many shapes. In Python, it allows different classes to use the same method name, but each can implement that method in a different way.
Polymorphism helps us write code that can work with objects of different classes, even if those classes have different behaviors:
class Person:
def get_details(self):
return "Details of a person."
class Student(Person):
def get_details(self):
return "Details of a student."
class Teacher(Person):
def get_details(self):
return "Details of a teacher."
# Example usage
def print_details(person):
print(person.get_details())
student = Student()
teacher = Teacher()
print_details(student)
print_details(teacher)
Details of a student.
Details of a teacher.
In this example, the function print_details()
can accept any object of type Person
, but it will call the appropriate get_details()
method based on whether the object is a Student
or a Teacher
.
Common Mistakes and Best Practices
While inheritance is powerful, it is easy to misuse. I will share some ideas to help you make the most of the ideas.
Avoiding surprises with overridden methods
When a child class overrides a method from its parent, the behavior can change.
For example, if the parent class Employee
has a calculate_pay()
method, and the Manager
child class overrides it without considering all scenarios, it might produce incorrect pay calculations.
The best practice, in this case, is to always test overridden methods thoroughly and document their behavior.
Choosing between inheritance and composition
I know this article is about inheritance, but it is not always the right approach. Sometimes, composition, where you build classes by combining objects rather than extending them might be a better fit with whatever you are doing.
To distll the differences in the most basic way, think that:
-
Inheritance refers to “Is-a” relationships. For example, a
Manager
is anEmployee
. -
Composition refers to “Has-a” relationships. For example, a
Car
has anEngine
.
So, how do you know when composition is the best approach to use? Use composition when the relationship is not strictly hierarchical and/or when you want to reduce tight coupling between classes.
Or, we could also say that, while inheritance models relationships, composition focuses on functionality. To help, consider this:
-
Use inheritance when objects are naturally hierarchical. For example,
Animal > Bird > Parrot
. -
Use composition when objects share functionality but are not related. For example, a
Printer
andScanner
both use aDeviceManager
.
Avoid deep inheritance chains
Deep inheritance chains (many levels of parent-child relationships) can make your code hard to read and maintain. This is a problem because changes to a parent class may unintentionally affect many child classes. Also, debugging becomes complex as behavior is spread across multiple levels.
The best practice in this case is to keep hierarchies shallow. Also, consider using composition (as I mentioned earlier) or breaking a chain into separate hierarchies if you find it is becoming too deep.
Conclusion
Inheritance is a major pillar of object-oriented programming that enables developers like you to create reusable, modular, and scalable code. If you can master inheritance, you will find it easy to simplify complex systems.
A good way to deepen your understanding is to try building inheritance structures in your projects. Start simple, then experiment with more complex hierarchies to see how they work in practice.
If you are eager to explore even deeper, you can check out our Programming Paradigm Concepts course for a deeper understanding of inheritance and other ideas. Our Python Developer career track is also a good resource that offers a comprehensive path to developing advanced programming skills that will equip you for software development.
Experienced data professional and writer who is passionate about empowering aspiring experts in the data space.
Python Inheritance FAQs
What is inheritance in Python?
Inheritance in Python is a mechanism that allows a class to inherit attributes and methods from another class, promoting code reusability and hierarchical class structures.
How does Python inheritance work?
Python inheritance works by defining a new class that inherits attributes and methods from an existing class, allowing for hierarchical class structures.
What are the types of inheritance in Python?
Python supports several types of inheritance, including single, multiple, multilevel, and hierarchical inheritance.
What is the difference between single and multiple inheritance in Python?
Single inheritance involves one parent class, while multiple inheritance allows a class to inherit from multiple parent classes.
How do you implement inheritance in Python?
Implement inheritance by defining a new class that specifies a parent class in its definition, using the syntax class ChildClass(ParentClass):
.
What are the benefits of using inheritance in Python?
Inheritance promotes code reusability, allows for the creation of hierarchical class structures, and supports polymorphism, making code more flexible and maintainable.
How do you implement multiple inheritance in Python?
Multiple inheritance is implemented by defining a class that inherits from more than one base class, with Python handling method resolution using the method resolution order (MRO).

blog
6 Python Best Practices for Better Code
tutorial
Python Abstract Classes: A Comprehensive Guide with Examples
tutorial
Intro to Multiple Inheritance & super()
tutorial
Object-Oriented Programming in Python (OOP): Tutorial
tutorial
How to Document Python Code
tutorial