Module 3: Inheritance
Parent Classes, Child Classes & Code Reuse — Python
Edition
Object-Oriented Programming Fundamentals · Healthcare Domain ·
Beginner Level
OOP FUNDAMENTALS PYTHON HEALTHCARE DOMAIN
The Problem: Code Duplication
Without Inheritance
Imagine building a hospital management
system. A Doctor class and a Nurse class
would both need to repeat the same
attributes and methods — over and over
again.
name, age, and emp_id defined twice
display_info() method copied into
every class
Adding a phone field means editing
every single class
More classes = more duplication =
more bugs
Code duplication is the enemy of
maintainability. One change
should never require updates in
ten places.
The Solution: Inheritance
Inheritance lets a child class automatically receive all the attributes and
methods of a parent class. The child only adds what is new or different.
Define Once Reuse Everywhere
Common attributes like name, Every child class automatically
age, and emp_id live in the inherits all parent attributes and
parent class only. methods without rewriting them.
Extend Freely
Each child adds only what makes it unique — a specialization for
Doctor, a ward for Nurse.
In Python, you declare inheritance by putting the parent class name inside
parentheses: class Doctor(HospitalStaff):
Complete Inheritance Example
HospitalStaff → Doctor & Nurse
class HospitalStaff:
def __init__(self, name, age, emp_id):
[Link] = name
[Link] = age
self.emp_id = emp_id
def display_info(self):
print(f"Name: {[Link]} | Age: {[Link]} | ID: {self.emp_id}")
class Doctor(HospitalStaff): # Inherits from HospitalStaff
def __init__(self, name, age, emp_id, specialization):
super().__init__(name, age, emp_id) # Call parent constructor
[Link] = specialization
def prescribe(self):
print(f"Dr. {[Link]} prescribed medication.")
def display(self):
self.display_info() # Reuse parent method
print(f"Specialization: {[Link]}")
class Nurse(HospitalStaff):
def __init__(self, name, age, emp_id, ward):
super().__init__(name, age, emp_id)
[Link] = ward
def assist_surgery(self):
print(f"{[Link]} is assisting in surgery.")
def display(self):
self.display_info()
print(f"Ward: {[Link]}")
Key Syntax: super().__init__(...) calls the parent's __init__. Always call it in the child's __init__ to ensure parent attributes are
properly initialized. Forgetting super() means the child won't have the parent's attributes.
Running the Example
Creating Objects & Calling Methods Output
d1 = Doctor("Sharma", 45, 1001, "Cardiology") --- Doctor ---
n1 = Nurse("Priya", 30, 2001, "ICU") Name: Sharma | Age: 45 | ID: 1001
Specialization: Cardiology
print("--- Doctor ---") Dr. Sharma prescribed medication.
[Link]()
[Link]() --- Nurse ---
print("\n--- Nurse ---") Name: Priya | Age: 30 | ID: 2001
[Link]() Ward: ICU
n1.assist_surgery() Priya is assisting in surgery.
Inherited from Parent Added by Child
display_info() is defined once in HospitalStaff and reused prescribe() belongs only to Doctor. assist_surgery() belongs
by both Doctor and Nurse without any duplication. only to Nurse. Each child extends the parent uniquely.
Types of Inheritance in Python
Python supports all major forms of inheritance. Understanding each type helps you design clean, scalable class hierarchies.
Python's Method Resolution Order (MRO) handles conflicts in multiple and hybrid inheritance using the C3 linearization algorithm.
Check it with ClassName.__mro__.
Single Inheritance
The Simplest Form — One Child, One Parent
Structure: A → B
One child class inherits from exactly one
parent class. This is the most common
and straightforward form of inheritance.
class Doctor(HospitalStaff):
pass
Even with just pass, the Doctor class
immediately has access to all of
HospitalStaff's attributes and methods.
Single inheritance is the safest
and most readable form. Start
here before exploring more
complex patterns.
Multilevel Inheritance
A Chain: Person → HospitalStaff → Surgeon
class Person:
def __init__(self, name, age):
[Link] = name; [Link] = age
class HospitalStaff(Person):
def __init__(self, name, age, emp_id):
super().__init__(name, age)
self.emp_id = emp_id
class Surgeon(HospitalStaff):
def __init__(self, name, age, emp_id, specialty):
super().__init__(name, age, emp_id)
[Link] = specialty
def operate(self):
print(f"Dr. {[Link]} ({[Link]}) performing surgery.")
Surgeon
Adds specialty and operate()
method
HospitalStaff
Inherits Person; adds
emp_id
Person
Holds name and age
attributes
Each level in the chain calls super().__init__() to pass attributes up the chain. Surgeon inherits from HospitalStaff, which inherits from
Person — giving Surgeon all three levels of attributes.
Hierarchical Inheritance
Multiple Children, One Shared Parent
Structure: One Parent → Many Children
Multiple child classes all inherit from the same single parent.
Each child gets the parent's attributes and adds its own unique
behavior.
class Doctor(HospitalStaff): pass
class Nurse(HospitalStaff): pass
class Technician(HospitalStaff): pass
All three classes — Doctor, Nurse, and Technician — share the
same HospitalStaff foundation: name, age, emp_id, and
display_info().
This is the most common pattern in real-world
systems. A single base class defines shared behavior
for an entire family of related classes.
Multiple Inheritance
Python Supports Inheriting from Two or More Parents
class Researcher:
def publish_paper(self):
print("Paper published.")
class Clinician:
def treat_patient(self):
print("Patient treated.")
class MedicalProfessor(Researcher, Clinician): # Two parents!
def teach(self):
print("Teaching medical students.")
mp = MedicalProfessor()
mp.publish_paper() # From Researcher
mp.treat_patient() # From Clinician
[Link]() # Own method
From Researcher From Clinician Own Method
publish_paper() — inherited directly treat_patient() — inherited directly teach() — defined in MedicalProfessor
itself
MRO: Python uses the C3 linearization algorithm to resolve method conflicts. Check the order with
MedicalProfessor.__mro__ or [Link]().
Inheritance Types — Summary
Type Structure Python Support
Single A→B ✅ Yes
Multilevel A→B→C ✅ Yes
Hierarchical A → B, A → C ✅ Yes
Multiple A, B → C ✅ Yes (MRO handles conflicts)
Hybrid Mix of above ✅ Yes (MRO handles diamond)
Python is one of the few languages that fully supports multiple inheritance. The Method Resolution Order (MRO) ensures
predictable behavior even in complex diamond inheritance scenarios.
Accessing Parent Members
with super()
Python's super() function returns a proxy object that lets you call the
parent's methods — without hardcoding the parent class name.
Overriding and Extending Why Use super()?
Avoids hardcoding the parent
class HospitalStaff: class name
def display_info(self):
Works correctly with multiple
print("Staff info")
inheritance and MRO
Most critical use:
class Doctor(HospitalStaff):
def display_info(self): # super().__init__() in constructors
Overrides parent Lets you extend parent
super().display_info() # behavior rather than replace it
Call parent version first
print("Plus doctor-specific
Tip: super() also works in
info")
__init__ to call the parent
constructor:
super().__init__(name,
age). This is the most
common and important
use for beginners.
isinstance() and issubclass()
Checking Object Relationships at Runtime
Code Example What Each Function Does
d1 = Doctor("Sharma", 45, 1001, "Cardiology") isinstance(obj, issubclass(Child,
Class) Parent)
print(isinstance(d1, Doctor)) # True
Returns True if obj is an Returns True if Child is a
print(isinstance(d1, HospitalStaff)) # True (parent!)
instance of Class or any subclass of Parent.
print(issubclass(Doctor, HospitalStaff)) # True
of its subclasses. Works Checks class
up the entire inheritance relationships, not object
chain. instances.
Key Insight: isinstance(d1, HospitalStaff) returns True
even though d1 is a Doctor object. A child object IS
an instance of its parent class too.
Constructor Chaining in Inheritance
How super().__init__() Flows Through the Chain
class Person:
def __init__(self, name):
[Link] = name
print(f"1. Person: {[Link]}")
class HospitalStaff(Person):
def __init__(self, name, emp_id):
super().__init__(name)
self.emp_id = emp_id
print(f"2. Staff: ID {self.emp_id}")
class Doctor(HospitalStaff):
def __init__(self, name, emp_id, spec):
super().__init__(name, emp_id)
[Link] = spec
print(f"3. Doctor: {[Link]}")
d = Doctor("Sharma", 1001, "Cardiology")
Output — Execution Order
Warning — #1 Inheritance Mistake: If you forget
super().__init__() in the child, the parent's attributes
1. Person: Sharma
(name, emp_id) will NOT exist on the object. You'll
2. Staff: ID 1001
get an AttributeError at runtime.
3. Doctor: Cardiology
Notice the order: Grandparent first, then Parent, then Child.
Each super().__init__() call triggers the next level up before
continuing.
Constructor Chaining — Visual Flow
Doctor.__init_ HospitalStaff. Person.__init_
_ __init__ _
Each constructor in the chain does its own work and then delegates upward via super(). The chain unwinds from the top (Person)
back down to the bottom (Doctor), setting attributes at each level.
01 02
Doctor.__init__ called HospitalStaff.__init__ called
Receives name, emp_id, spec. Calls super().__init__(name, Receives name, emp_id. Calls super().__init__(name) before
emp_id) before setting [Link]. setting self.emp_id.
03 04
Person.__init__ called Chain unwinds
Receives name. Sets [Link]. No more super() calls — chain Execution returns to HospitalStaff (sets emp_id), then to Doctor
complete. (sets spec). Object is fully initialized.
Exercise 1: Hospital Staff
Hierarchy
Hands-On Practice — HospitalStaff → Doctor & Nurse
Problem: Create HospitalStaff (name, emp_id), Doctor (specialization), and
Nurse (ward). Each has a display() method. Demo with 2 doctors and 1
nurse.
Exercise 1 — Complete Solution
class HospitalStaff:
def __init__(self, name, emp_id):
[Link] = name; self.emp_id = emp_id
def display_info(self):
print(f"Name: {[Link]} | ID: {self.emp_id}")
class Doctor(HospitalStaff):
def __init__(self, name, emp_id, spec):
super().__init__(name, emp_id); [Link] = spec
def display(self):
self.display_info()
print(f"Specialization: {[Link]}")
class Nurse(HospitalStaff):
def __init__(self, name, emp_id, ward):
super().__init__(name, emp_id); [Link] = ward
def display(self):
self.display_info()
print(f"Ward: {[Link]}")
d1 = Doctor("Sharma", 1001, "Cardiology")
d2 = Doctor("Patel", 1002, "Neurology")
n1 = Nurse("Priya", 2001, "ICU")
Expected Output What to Notice
display_info() is defined once in HospitalStaff and reused by
=== Doctors === both children
Name: Sharma | ID: 1001
Each child calls super().__init__() to initialize shared
Specialization: Cardiology
attributes
Name: Patel | ID: 1002 Doctor and Nurse each add only their unique attribute
Specialization: Neurology Both children override display() to show their specific field
=== Nurses ===
This is hierarchical inheritance in action — one
Name: Priya | ID: 2001
parent, two children, zero code duplication for shared
Ward: ICU
behavior.
Exercise 2: Multilevel — Person → Employee →
Manager
Three-Level Constructor Chaining
Problem: Build a three-level inheritance chain with constructor chaining. Show output at each level as objects are created.
class Person:
def __init__(self, name, age):
[Link] = name; [Link] = age
print(f"Person created: {[Link]}")
class Employee(Person):
def __init__(self, name, age, emp_id, salary):
super().__init__(name, age)
self.emp_id = emp_id; [Link] = salary
print(f"Employee created: ID {self.emp_id}")
class Manager(Employee):
def __init__(self, name, age, emp_id, salary, dept, team_size):
super().__init__(name, age, emp_id, salary)
[Link] = dept; self.team_size = team_size
print(f"Manager created: {[Link]}")
def display(self):
print(f"Name: {[Link]} | Age: {[Link]}")
print(f"ID: {self.emp_id} | Salary: {[Link]}")
print(f"Dept: {[Link]} | Team: {self.team_size}")
Creating the Object Output
print("--- Creating Manager ---") --- Creating Manager ---
m = Manager("Sharma", 50, 1001, 150000, "Cardiology", Person created: Sharma
12) Employee created: ID 1001
print("\n--- Details ---") Manager created: Cardiology
[Link]()
--- Details ---
Name: Sharma | Age: 50
ID: 1001 | Salary: 150000
Dept: Cardiology | Team: 12
Notice the print statements fire from the top of the chain downward — Person first, then Employee, then Manager. This
confirms the order in which super().__init__() calls execute.
Exercise 3: Vehicle Fleet
Mixed Practice — Ambulance & SupplyTruck
Problem: Build a Vehicle base class (brand, year). Create Ambulance (hospital, siren_on) and SupplyTruck (cargo) as children.
Implement activate_siren() and load_cargo(kg).
class Vehicle:
def __init__(self, brand, year):
[Link] = brand; [Link] = year
def show_vehicle(self):
print(f"{[Link]} ({[Link]})")
class Ambulance(Vehicle):
def __init__(self, brand, year, hospital):
super().__init__(brand, year)
[Link] = hospital; self.siren_on = False
def activate_siren(self):
self.siren_on = True
print(f"{[Link]} siren ACTIVATED for {[Link]}")
def display(self):
self.show_vehicle()
print(f"Hospital: {[Link]} | Siren: {'ON' if self.siren_on else 'OFF'}")
class SupplyTruck(Vehicle):
def __init__(self, brand, year):
super().__init__(brand, year); [Link] = 0
def load_cargo(self, kg):
[Link] += kg
print(f"{[Link]} loaded {kg} kg (Total: {[Link]} kg)")
def display(self):
self.show_vehicle()
print(f"Cargo: {[Link]} kg")
Exercise 3 — Running the Fleet
Demo Code
a1 = Ambulance("Tata Winger", 2023,
"City General")
a1.activate_siren()
t1 = SupplyTruck("Mahindra Bolero",
2022)
t1.load_cargo(250); t1.load_cargo(150)
print("\n--- Fleet Status ---")
[Link](); print(); [Link]()
Expected Output
Tata Winger siren ACTIVATED for City
General
Mahindra Bolero loaded 250 kg
(Total: 250 kg)
Mahindra Bolero loaded 150 kg
(Total: 400 kg) Key Observations: siren_on starts as False and is toggled by
activate_siren(). Cargo accumulates across multiple load_cargo() calls.
--- Fleet Status --- Both children reuse show_vehicle() from Vehicle.
Tata Winger (2023)
Hospital: City General | Siren: ON
Mahindra Bolero (2022)
Cargo: 400 kg
Common Mistakes & How to
Avoid Them
❌ Forgetting super().__init__()
The #1 inheritance mistake. Without it, the parent's attributes are never
set. Any access to [Link] or self.emp_id will raise an AttributeError
at runtime.
❌ Wrong Argument Order in super()
The arguments passed to super().__init__() must match the parent's
__init__ signature exactly. Passing them in the wrong order silently
assigns wrong values.
❌ Hardcoding the Parent Class Name
Writing HospitalStaff.__init__(self, name) instead of
super().__init__(name) breaks multiple inheritance and MRO. Always
use super().
❌ Calling super() After Setting Child Attributes
Always call super().__init__() first in the child constructor. Setting child
attributes before the parent is initialized can cause subtle bugs.
Quick Reference — Syntax at a Glance
Core Inheritance Syntax All Inheritance Patterns
class Parent: # Single
def __init__(self, x): class B(A): pass
self.x = x
# Multilevel
class Child(Parent): # Inherits from Parent class C(B): pass # C inherits B inherits A
def __init__(self, x, y):
super().__init__(x) # Call parent constructor # Hierarchical
self.y = y class B(A): pass
class C(A): pass
def show(self):
super().show() # Call parent method # Multiple
class C(A, B): pass # MRO resolves conflicts
Checking Relationships
You can always verify the MRO with
isinstance(obj, Parent) # True for child objects too
print([Link]()) to see exactly which class
issubclass(Child, Parent) # True
Python will look in first when resolving a method call.
ClassName.__mro__ # View method resolution order
Key Concepts Summary
Concept Remember This
Inheritance class Child(Parent): — child gets all parent members automatically
super() Calls parent methods/constructor; always use in __init__
Constructor order Grandparent → Parent → Child (via super() chain)
Single class B(A): — one parent, simplest form
Multilevel A → B → C — chain of super() calls at each level
Multiple class C(A, B): — Python supports; MRO resolves conflicts
isinstance() isinstance(obj, Parent) returns True for child objects too
#1 Mistake Forgetting super().__init__() — parent attrs won't exist
Inheritance vs. No Inheritance
The Before and After of Good OOP Design
The DRY Principle
Don't Repeat Yourself. Inheritance is one of the most powerful
tools for achieving DRY code in object-oriented programming.
Single source of truth One change, everywhere Fewer bugs, easier testing
Shared attributes and methods live updated Less duplicated code means
in exactly one place — the parent Add a phone field to HospitalStaff fewer places for bugs to hide and
class. and every child class gets it fewer tests to write.
instantly.
Module 3 — Wrap Up
What You've Learned: Inheritance in Python
Inheritance Basics Four Types
Child classes inherit all parent attributes and methods. Use Single, Multilevel, Hierarchical, and Multiple inheritance —
class Child(Parent): syntax. Only define what's new or Python supports all of them. MRO handles conflicts in
different in the child. multiple and hybrid cases.
super() Mastery Runtime Checks
Always call super().__init__() first in child constructors. Use isinstance() and issubclass() let you inspect class
super().method() to extend — not replace — parent relationships at runtime. Child objects are instances of their
behavior. parent classes too.
Next Steps: Practice the three exercises until constructor chaining feels natural. Then explore Module 4: Polymorphism
— where inherited methods take on different forms depending on the object calling them.