Python Inheritance
Learn about inheritance in Python - how to create new classes based on existing classes.
Python Inheritance
Inheritance allows us to define a class that inherits all the methods and properties from another class.
Parent class is the class being inherited from, also called base class.
Child class is the class that inherits from another class, also called derived class.
Create a Parent Class
Any class can be a parent class, so the syntax is the same as creating any other class:
Example - Create a class named Person, with firstname and lastname properties, and a printname method:
class Person:
def __init__(self, fname, lname):
self.firstname = fname
self.lastname = lname
def printname(self):
print(self.firstname, self.lastname)
# Use the Person class to create an object, and then execute the printname method:
x = Person("John", "Doe")
x.printname()Create a Child Class
To create a class that inherits the functionality from another class, send the parent class as a parameter when creating the child class:
Example - Create a class named Student, which will inherit the properties and methods from the Person class:
class Person:
def __init__(self, fname, lname):
self.firstname = fname
self.lastname = lname
def printname(self):
print(self.firstname, self.lastname)
class Student(Person):
pass
x = Student("Mike", "Olsen")
x.printname()Note: Use the pass keyword when you do not want to add any other properties or methods to the class.
Now the Student class has the same properties and methods as the Person class.
Add the __init__() Function
So far we have created a child class that inherits the properties and methods from its parent.
We want to add the __init__() function to the child class (instead of the pass keyword).
Note: The __init__() function is called automatically every time the class is being used to create a new object.
Example - Add the __init__() function to the Student class:
class Person:
def __init__(self, fname, lname):
self.firstname = fname
self.lastname = lname
def printname(self):
print(self.firstname, self.lastname)
class Student(Person):
def __init__(self, fname, lname):
# add properties etc.
x = Student("Mike", "Olsen")When you add the __init__() function, the child class will no longer inherit the parent's __init__() function.
Note: The child's __init__() function overrides the inheritance of the parent's __init__() function.
To keep the inheritance of the parent's __init__() function, add a call to the parent's __init__() function:
Example
class Person:
def __init__(self, fname, lname):
self.firstname = fname
self.lastname = lname
def printname(self):
print(self.firstname, self.lastname)
class Student(Person):
def __init__(self, fname, lname):
Person.__init__(self, fname, lname)
x = Student("Mike", "Olsen")
x.printname()Now we have successfully added the __init__() function, and kept the inheritance of the parent class, and we are ready to add functionality in the __init__() function.
Use the super() Function
Python also has a super() function that will make the child class inherit all the methods and properties from its parent:
Example
class Person:
def __init__(self, fname, lname):
self.firstname = fname
self.lastname = lname
def printname(self):
print(self.firstname, self.lastname)
class Student(Person):
def __init__(self, fname, lname):
super().__init__(fname, lname)
x = Student("Mike", "Olsen")
x.printname()By using the super() function, you do not have to use the name of the parent element, it will automatically inherit the methods and properties from its parent.
Add Properties
Example - Add a property called graduationyear to the Student class:
class Person:
def __init__(self, fname, lname):
self.firstname = fname
self.lastname = lname
def printname(self):
print(self.firstname, self.lastname)
class Student(Person):
def __init__(self, fname, lname):
super().__init__(fname, lname)
self.graduationyear = 2019
x = Student("Mike", "Olsen")
print(x.graduationyear)In the example below, the year 2019 should be a variable, and passed into the Student class when creating student objects. To do so, add another parameter in the __init__() function:
Example - Add a year parameter, and pass the correct year when creating objects:
class Person:
def __init__(self, fname, lname):
self.firstname = fname
self.lastname = lname
def printname(self):
print(self.firstname, self.lastname)
class Student(Person):
def __init__(self, fname, lname, year):
super().__init__(fname, lname)
self.graduationyear = year
x = Student("Mike", "Olsen", 2019)
print(x.graduationyear)Add Methods
Example - Add a method called welcome to the Student class:
class Person:
def __init__(self, fname, lname):
self.firstname = fname
self.lastname = lname
def printname(self):
print(self.firstname, self.lastname)
class Student(Person):
def __init__(self, fname, lname, year):
super().__init__(fname, lname)
self.graduationyear = year
def welcome(self):
print("Welcome", self.firstname, self.lastname, "to the class of", self.graduationyear)
x = Student("Mike", "Olsen", 2019)
x.welcome()If you add a method in the child class with the same name as a function in the parent class, the inheritance of the parent method will be overridden.
Method Overriding
Child classes can override methods from their parent classes:
Example - Method overriding:
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound"
def move(self):
return f"{self.name} moves"
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name)
self.breed = breed
def speak(self): # Override parent method
return f"{self.name} barks"
def fetch(self): # New method specific to Dog
return f"{self.name} fetches the ball"
class Cat(Animal):
def __init__(self, name, color):
super().__init__(name)
self.color = color
def speak(self): # Override parent method
return f"{self.name} meows"
def climb(self): # New method specific to Cat
return f"{self.name} climbs the tree"
# Usage
dog = Dog("Buddy", "Golden Retriever")
cat = Cat("Whiskers", "Orange")
print(dog.speak()) # Buddy barks
print(dog.move()) # Buddy moves (inherited)
print(dog.fetch()) # Buddy fetches the ball
print(cat.speak()) # Whiskers meows
print(cat.climb()) # Whiskers climbs the treeMultiple Inheritance
Python supports multiple inheritance, where a class can inherit from multiple parent classes:
Example - Multiple inheritance:
class Mammal:
def __init__(self, name):
self.name = name
self.warm_blooded = True
def breathe(self):
return f"{self.name} breathes air"
class Carnivore:
def __init__(self):
self.diet = "meat"
def hunt(self):
return f"Hunting for prey"
class Lion(Mammal, Carnivore):
def __init__(self, name, pride_size):
Mammal.__init__(self, name)
Carnivore.__init__(self)
self.pride_size = pride_size
def roar(self):
return f"{self.name} roars loudly"
def hunt(self): # Override Carnivore's hunt method
return f"{self.name} hunts with the pride of {self.pride_size}"
# Usage
lion = Lion("Simba", 8)
print(lion.breathe()) # Simba breathes air
print(lion.hunt()) # Simba hunts with the pride of 8
print(lion.roar()) # Simba roars loudly
print(f"Diet: {lion.diet}") # Diet: meatMethod Resolution Order (MRO)
When using multiple inheritance, Python uses Method Resolution Order to determine which method to call:
Example - Understanding MRO:
class A:
def method(self):
return "Method from A"
class B(A):
def method(self):
return "Method from B"
class C(A):
def method(self):
return "Method from C"
class D(B, C):
pass
# Check the Method Resolution Order
print(D.__mro__)
# (, , , , )
d = D()
print(d.method()) # Method from B (B comes before C in MRO)
# You can also check MRO with mro() method
print(D.mro()) Abstract Base Classes
Use abstract base classes to define interfaces that subclasses must implement:
Example - Abstract base class:
from abc import ABC, abstractmethod
class Vehicle(ABC):
def __init__(self, make, model):
self.make = make
self.model = model
@abstractmethod
def start_engine(self):
pass
@abstractmethod
def stop_engine(self):
pass
def get_info(self):
return f"{self.make} {self.model}"
class Car(Vehicle):
def __init__(self, make, model, doors):
super().__init__(make, model)
self.doors = doors
def start_engine(self):
return f"{self.make} {self.model} engine started with key"
def stop_engine(self):
return f"{self.make} {self.model} engine stopped"
class Motorcycle(Vehicle):
def __init__(self, make, model, engine_size):
super().__init__(make, model)
self.engine_size = engine_size
def start_engine(self):
return f"{self.make} {self.model} engine started with button"
def stop_engine(self):
return f"{self.make} {self.model} engine stopped"
# Usage
car = Car("Toyota", "Camry", 4)
bike = Motorcycle("Honda", "CBR", 600)
print(car.get_info()) # Toyota Camry
print(car.start_engine()) # Toyota Camry engine started with key
print(bike.start_engine()) # Honda CBR engine started with button
# This would raise an error:
# vehicle = Vehicle("Generic", "Vehicle") # TypeError: Can't instantiate abstract classPractical Example: Employee Management System
Complete inheritance example:
from abc import ABC, abstractmethod
from datetime import datetime
class Employee(ABC):
def __init__(self, employee_id, name, email):
self.employee_id = employee_id
self.name = name
self.email = email
self.hire_date = datetime.now()
@abstractmethod
def calculate_salary(self):
pass
def get_info(self):
return f"ID: {self.employee_id}, Name: {self.name}, Email: {self.email}"
def years_of_service(self):
return (datetime.now() - self.hire_date).days // 365
class FullTimeEmployee(Employee):
def __init__(self, employee_id, name, email, annual_salary):
super().__init__(employee_id, name, email)
self.annual_salary = annual_salary
def calculate_salary(self):
return self.annual_salary / 12 # Monthly salary
class PartTimeEmployee(Employee):
def __init__(self, employee_id, name, email, hourly_rate, hours_per_week):
super().__init__(employee_id, name, email)
self.hourly_rate = hourly_rate
self.hours_per_week = hours_per_week
def calculate_salary(self):
return self.hourly_rate * self.hours_per_week * 4 # Monthly salary
class Manager(FullTimeEmployee):
def __init__(self, employee_id, name, email, annual_salary, team_size):
super().__init__(employee_id, name, email, annual_salary)
self.team_size = team_size
self.team_members = []
def add_team_member(self, employee):
self.team_members.append(employee)
def calculate_salary(self):
base_salary = super().calculate_salary()
bonus = self.team_size * 500 # $500 bonus per team member
return base_salary + bonus
def get_team_info(self):
team_info = f"Manager: {self.name}\nTeam Size: {self.team_size}\nTeam Members:\n"
for member in self.team_members:
team_info += f" - {member.name}\n"
return team_info
class Contractor(Employee):
def __init__(self, employee_id, name, email, project_rate, contract_duration):
super().__init__(employee_id, name, email)
self.project_rate = project_rate
self.contract_duration = contract_duration # in months
def calculate_salary(self):
return self.project_rate / self.contract_duration
# Usage
# Create employees
ft_employee = FullTimeEmployee("FT001", "Alice Johnson", "alice@company.com", 60000)
pt_employee = PartTimeEmployee("PT001", "Bob Smith", "bob@company.com", 25, 20)
manager = Manager("MG001", "Carol Davis", "carol@company.com", 80000, 3)
contractor = Contractor("CT001", "David Wilson", "david@company.com", 15000, 6)
# Add team members to manager
manager.add_team_member(ft_employee)
manager.add_team_member(pt_employee)
# Display information
employees = [ft_employee, pt_employee, manager, contractor]
for emp in employees:
print(f"\n{emp.get_info()}")
print(f"Monthly Salary: ${emp.calculate_salary():.2f}")
print(f"Years of Service: {emp.years_of_service()}")
if isinstance(emp, Manager):
print(emp.get_team_info())super() and Method Overriding
A subclass can override a parent method and still call the parent version with super().
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "some sound"
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # run the parent constructor
self.breed = breed
def speak(self): # override
return "Woof!"
d = Dog("Rex", "Lab")
print(d.name, d.breed, d.speak()) # Rex Lab Woof!
print(isinstance(d, Animal)) # TrueTypes of Inheritance
| Type | Shape |
|---|---|
| Single | B inherits A |
| Multilevel | C inherits B inherits A |
| Multiple | C inherits A and B |
| Hierarchical | B and C both inherit A |
With multiple inheritance, Python resolves method lookup using the MRO (Method Resolution Order). Inspect it with ClassName.__mro__.
Try It Yourself
Exercise 1: Make a Cat subclass of Animal whose speak() returns "Meow".
Show solution
class Cat(Animal):
def speak(self):
return "Meow"
print(Cat("Tom").speak()) # MeowExercise 2: In a subclass constructor, call the parent's __init__ with super().
Show solution
class Bird(Animal):
def __init__(self, name, can_fly):
super().__init__(name)
self.can_fly = can_fly
b = Bird("Sky", True)
print(b.name, b.can_fly) # Sky TrueKey Takeaways
- A subclass reuses and extends a parent class.
- Override methods to specialize; call
super()to reuse parent logic. isinstanceandissubclasstest relationships.
📘 Real-World Deep Dive
Inheritance models an "is-a" relationship. It's easy to over-use — prefer composition unless the subclass really does behave like the parent and adds new behaviour.
Real-Life Scenario
A small reporting hierarchy — <code>Report</code> base, two specialised subclasses with overrides and extra state.
Real-Life Example
from dataclasses import dataclass, field
@dataclass
class Report:
title: str
rows: list[list[str]] = field(default_factory=list)
def render(self) -> str:
return f"# {self.title}\n\n" + "\n".join(", ".join(r) for r in self.rows) + "\n"
@dataclass
class MarkdownReport(Report):
def render(self) -> str:
header = "| " + " | ".join(self.rows[0]) + " |\n"
sep = "|" + "|".join("---" for _ in self.rows[0]) + "|\n"
body = "".join("| " + " | ".join(r) + " |\n" for r in self.rows[1:])
return f"# {self.title}\n\n{header}{sep}{body}"
@dataclass
class TsvReport(Report):
delim: str = "\t"
def render(self) -> str:
return (
f"# {self.title}\n"
+ "\n".join(self.delim.join(r) for r in self.rows) + "\n"
)
base = Report("Users", [["name", "age"], ["Ada", "29"], ["Bo", "41"]])
md = MarkdownReport("Users", base.rows)
tsv = TsvReport("Users", base.rows, delim="\t")
print(md.render())
print(tsv.render())Expected Output
# Users
| name | age |
|---|---|
| Ada | 29 |
| Bo | 41 |
# Users
name age
Ada 29
Bo 41Common mistakes
- Don't use inheritance to share code; that's composition's job. Inherit only when you really mean "is-a".
- Calling
super().method(...)correctly is easy to forget; usesuper().__init__raises a TypError hint. - Multiple inheritance with MRO surprises — stick to mixins (
class Foo(Bar, JsonMixin)) and single inheritance.
🚀 Performance & Best Practices
- Method lookup in Python is a chain; deep hierarchies are slow per call but rarely matter.
- Use
type(self).__mro__to introspect the lookup order at runtime if needed. - Abstract base classes (
abc.ABC) are great for documenting interfaces, not for shared state.
🧪 Try It Yourself
- Add a
JsonReport(Report)subclass that usesjson.dumps(rows). - Refactor
MarkdownReport/TsvReportto a single class with aformat: strfield. - Write a base
CachedReportmixin and apply toMarkdownReport.