Inheritance is one of the most important concepts in Python Object-Oriented Programming. It allows one class to reuse attributes and methods from another class instead of writing the same code repeatedly.
In simple terms, inheritance creates a relationship between two classes. The existing class is commonly called the parent class or base class, while the new class is called the child class or derived class.
For example, suppose we have a general Vehicle class. A car and a motorcycle are both vehicles, so they may share common properties and behaviors.
Vehicle
|
├── Car
|
└── Motorcycle
Instead of defining common functionality separately inside both Car and Motorcycle, we can place it in the parent class and allow the child classes to inherit it.
Inheritance is a mechanism through which a child class can acquire attributes and methods from a parent class.
A basic Python inheritance structure looks like this:
class Parent:
# parent class members
pass
class Child(Parent):
# child class members
pass
The important part is:
class Child(Parent):
By placing Parent inside the parentheses, we tell Python that Child inherits from Parent.
Let’s create a simple parent class:
class Animal:
def eat(self):
print("Animal is eating")
Now create a child class:
class Dog(Animal):
pass
Create an object:
dog1 = Dog()
Even though eat() was not defined inside Dog, the dog can use it:
dog1.eat()
Output:
Animal is eating
This happens because Dog inherits the eat() method from Animal.
The relationship can be visualized as:
Animal
|
| inherits
↓
Dog
Animal.eat()
↓
Dog object can use eat()
The biggest benefit of inheritance is code reuse.
Suppose we have three types of employees:
All employees may have common information such as:
They may also share common methods such as:
display()calculate_annual_salary()Instead of duplicating these features in every class, we can create an Employee parent class.
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def display(self):
print("Name:", self.name)
print("Salary:", self.salary)
def annual_salary(self):
return self.salary * 12
Then create a child class:
class Developer(Employee):
pass
Now:
developer1 = Developer("Rahul", 60000)
The child object can use the inherited methods:
developer1.display()
print(developer1.annual_salary())
Output:
Name: Rahul
Salary: 60000
720000
The Developer class did not have to rewrite the common employee functionality.
It is useful to understand the terminology clearly.
| Term | Meaning |
|---|---|
| Parent Class | Class whose attributes and methods can be inherited |
| Base Class | Another name for parent class |
| Child Class | Class that inherits from another class |
| Derived Class | Another name for child class |
| Inheritance | Mechanism for creating an “is-a” relationship between classes |
For example:
class Animal:
pass
class Dog(Animal):
pass
Here:
Animal → Parent
Dog → Child
A child class can use methods defined in its parent class.
class Person:
def introduce(self):
print("I am a person")
class Student(Person):
pass
Now:
student1 = Student()
student1.introduce()
Output:
I am a person
The Student class inherits the introduce() method.
This allows common behavior to be written once in the parent class and reused by multiple child classes.
Inheritance can also work with constructors.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
class Student(Person):
pass
Now create a student:
student1 = Student("Rahul", 21)
The child object can access:
print(student1.name)
print(student1.age)
Output:
Rahul
21
The initialization logic comes from the parent class.
A child class does not have to contain only inherited functionality. It can add its own attributes and methods.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def display(self):
print("Name:", self.name)
print("Age:", self.age)
class Student(Person):
def study(self):
print(self.name, "is studying")
Create the object:
student1 = Student("Rahul", 21)
The object can use the inherited method:
student1.display()
And the child-specific method:
student1.study()
Output:
Name: Rahul
Age: 21
Rahul is studying
So inheritance does not mean the child class is limited to the parent’s functionality.
The child can:
A useful way to decide whether inheritance makes sense is to ask whether the child class is a type of the parent class.
For example:
Dog is an Animal
Car is a Vehicle
Manager is an Employee
Student is a Person
These relationships make intuitive sense.
For example:
class Vehicle:
pass
class Car(Vehicle):
pass
A car is a type of vehicle.
However, inheritance would usually not make sense for:
class Engine:
pass
class Car(Engine):
pass
A car has an engine rather than being an engine.
This distinction is important when designing object-oriented programs.
Consider a company application with different employee types.
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def annual_salary(self):
return self.salary * 12
class Developer(Employee):
pass
class Manager(Employee):
pass
Now both child classes automatically have access to:
annual_salary()
For example:
developer1 = Developer("Rahul", 60000)
manager1 = Manager("Priya", 80000)
print(developer1.annual_salary())
print(manager1.annual_salary())
Output:
720000
960000
The common logic exists only once in Employee.
A child class can also introduce its own constructor.
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
class Developer(Employee):
def __init__(self, name, salary, programming_language):
self.name = name
self.salary = salary
self.programming_language = programming_language
Now:
developer1 = Developer(
"Rahul",
60000,
"Python"
)
The developer has all three pieces of data:
name
salary
programming_language
However, there is a problem with this approach: the child has duplicated the initialization logic for name and salary.
Python provides super() to solve this problem.
Instead of rewriting the parent constructor, the child can call it using super().
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
class Developer(Employee):
def __init__(self, name, salary, programming_language):
super().__init__(name, salary)
self.programming_language = programming_language
Now create the object:
developer1 = Developer(
"Rahul",
60000,
"Python"
)
The following line:
super().__init__(name, salary)
calls the parent class’s constructor.
The parent initializes:
self.name
self.salary
Then the child initializes:
self.programming_language
This avoids duplicating the parent initialization code.
super() provides a convenient way to access functionality from a parent class or another class in the inheritance hierarchy.
For example:
class Animal:
def speak(self):
print("Animal makes a sound")
class Dog(Animal):
def speak(self):
super().speak()
print("Dog barks")
Create the object:
dog1 = Dog()
dog1.speak()
Output:
Animal makes a sound
Dog barks
The child method calls the parent implementation first:
super().speak()
and then adds its own behavior.
A child class can replace or customize a method inherited from its parent. This is called method overriding.
class Animal:
def speak(self):
print("Animal makes a sound")
class Dog(Animal):
def speak(self):
print("Dog barks")
Now:
dog1 = Dog()
dog1.speak()
Output:
Dog barks
The child class has its own implementation of speak(), so the inherited version is overridden for Dog objects.
This concept will be explored in much greater detail later in this lesson.
Inheritance works together with the encapsulation concepts you learned in the previous lesson.
For example:
class Employee:
def __init__(self, salary):
self.__salary = salary
def get_salary(self):
return self.__salary
class Manager(Employee):
pass
The Manager class inherits the public method:
get_salary()
and can use it:
manager1 = Manager(80000)
print(manager1.get_salary())
Output:
80000
The internal implementation of the parent’s private-style attribute remains part of the parent class design.
This demonstrates how OOP concepts work together rather than existing as completely separate features.
Inheritance should not be used simply because it allows code reuse.
An unnecessarily complicated inheritance hierarchy can make a program harder to understand.
For example, creating many levels of inheritance:
A
↓
B
↓
C
↓
D
↓
E
can make it difficult to understand where a particular method or attribute originated.
Inheritance should generally represent a meaningful relationship between classes.
If the relationship is better described as “has-a” rather than “is-a”, composition may be more appropriate.
Consider a car:
Car has an Engine
This is a composition relationship.
We might write:
class Engine:
pass
class Car:
def __init__(self):
self.engine = Engine()
But:
Dog is an Animal
can naturally be represented through inheritance:
class Animal:
pass
class Dog(Animal):
pass
Understanding the difference between is-a and has-a relationships helps you choose an appropriate object-oriented design.
class Dog(Animal):.super() can be used to access parent functionality.You now have the foundation of Python inheritance: parent classes, child classes, inherited methods, child-specific functionality, super(), and method overriding. In the next part, we will explore the different types of inheritance in Python, including single, multiple, multilevel, hierarchical, and hybrid inheritance with practical examples.
Python supports several types of inheritance. The type of inheritance depends on how the parent and child classes are connected.
The major types are:
Understanding these structures is important because different applications may require different relationships between classes.
Single inheritance occurs when one child class inherits from one parent class.
The structure is:
Parent
↓
Child
For example:
class Animal:
def eat(self):
print("Animal is eating")
class Dog(Animal):
def bark(self):
print("Dog is barking")
Here, Dog inherits from Animal.
Create an object:
dog1 = Dog()
dog1.eat()
dog1.bark()
Output:
Animal is eating
Dog is barking
The eat() method comes from the parent class, while bark() belongs to the child class.
Single inheritance can also be used when the parent class contains a constructor.
class Person:
def __init__(self, name):
self.name = name
def introduce(self):
print("My name is", self.name)
class Student(Person):
def study(self):
print(self.name, "is studying")
Create the object:
student1 = Student("Rahul")
The student can use both inherited and child-specific functionality:
student1.introduce()
student1.study()
Output:
My name is Rahul
Rahul is studying
Single inheritance is the simplest form of inheritance and is often the easiest structure to understand and maintain.
Multiple inheritance occurs when one child class inherits from more than one parent class.
The structure is:
Parent A Parent B
\ /
\ /
Child
Python allows a class to inherit from multiple classes.
class Father:
def skills_from_father(self):
print("Driving")
class Mother:
def skills_from_mother(self):
print("Cooking")
class Child(Father, Mother):
pass
Now create an object:
child1 = Child()
child1.skills_from_father()
child1.skills_from_mother()
Output:
Driving
Cooking
The Child class receives functionality from both parent classes.
Consider a practical example involving software development.
class PythonSkills:
def python(self):
print("Python programming")
class DatabaseSkills:
def sql(self):
print("SQL programming")
class DataAnalyst(PythonSkills, DatabaseSkills):
def analysis(self):
print("Data analysis")
Now:
analyst1 = DataAnalyst()
analyst1.python()
analyst1.sql()
analyst1.analysis()
Output:
Python programming
SQL programming
Data analysis
This is a useful conceptual example because a data analyst may need multiple categories of skills.
Multilevel inheritance occurs when inheritance continues through multiple levels.
The structure looks like:
Grandparent
↓
Parent
↓
Child
For example:
class Animal:
def eat(self):
print("Eating")
class Dog(Animal):
def bark(self):
print("Barking")
class Puppy(Dog):
def play(self):
print("Playing")
Now:
puppy1 = Puppy()
The Puppy object can access methods from its immediate parent and the higher-level ancestor:
puppy1.eat()
puppy1.bark()
puppy1.play()
Output:
Eating
Barking
Playing
The inheritance chain is:
Animal
↓
Dog
↓
Puppy
Puppy inherits from Dog, and Dog inherits from Animal.
Consider a company application:
class Employee:
def work(self):
print("Employee is working")
class Developer(Employee):
def code(self):
print("Developer is coding")
class SeniorDeveloper(Developer):
def review_code(self):
print("Reviewing code")
Create:
developer1 = SeniorDeveloper()
Now the object can use:
developer1.work()
developer1.code()
developer1.review_code()
Output:
Employee is working
Developer is coding
Reviewing code
This demonstrates how functionality can progressively become more specialized at each level.
Hierarchical inheritance occurs when multiple child classes inherit from the same parent class.
The structure is:
Parent
/ \
/ \
Child A Child B
For example:
class Animal:
def eat(self):
print("Animal is eating")
class Dog(Animal):
def bark(self):
print("Dog is barking")
class Cat(Animal):
def meow(self):
print("Cat is meowing")
Now:
dog1 = Dog()
cat1 = Cat()
The dog can use:
dog1.eat()
dog1.bark()
The cat can use:
cat1.eat()
cat1.meow()
Both child classes share the functionality inherited from Animal, while each has its own specialized behavior.
Consider a general employee class:
class Employee:
def login(self):
print("Employee logged in")
class Developer(Employee):
def write_code(self):
print("Writing code")
class Manager(Employee):
def manage_team(self):
print("Managing team")
class Analyst(Employee):
def analyze_data(self):
print("Analyzing data")
Now there are three child classes:
Employee
|
├── Developer
├── Manager
└── Analyst
All three inherit:
login()
but each provides its own specialized behavior.
Hybrid inheritance is a combination of two or more types of inheritance.
For example, a hierarchy can combine multiple inheritance with multilevel or hierarchical inheritance.
A simplified structure could look like:
A
/ \
B C
\ /
D
Here:
B inherits from A.C inherits from A.D inherits from both B and C.This combines hierarchical and multiple inheritance.
For example:
class A:
def method_a(self):
print("A")
class B(A):
def method_b(self):
print("B")
class C(A):
def method_c(self):
print("C")
class D(B, C):
def method_d(self):
print("D")
Now:
object1 = D()
object1.method_a()
object1.method_b()
object1.method_c()
object1.method_d()
Output:
A
B
C
D
This is a hybrid inheritance structure.
Multiple inheritance introduces an important concept called Method Resolution Order, commonly abbreviated as MRO.
When a class inherits from multiple classes, Python needs to determine where it should search for a method.
Consider:
class A:
def show(self):
print("A")
class B(A):
def show(self):
print("B")
class C(A):
def show(self):
print("C")
class D(B, C):
pass
Now:
object1 = D()
object1.show()
Python needs to determine whether it should use B.show(), C.show(), or A.show().
Because D lists B before C:
class D(B, C):
Python’s MRO determines the search order.
You can inspect the MRO using:
print(D.mro())
or:
print(D.__mro__)
The exact output contains the class hierarchy Python uses when looking for methods.
Suppose two parent classes contain methods with the same name.
class A:
def show(self):
print("A")
class B:
def show(self):
print("B")
class C(A, B):
pass
Now:
object1 = C()
object1.show()
Python searches according to the MRO, so the method from the appropriate class in the resolution order is selected.
You can inspect that order:
print(C.mro())
Understanding MRO becomes particularly important when working with multiple inheritance and the super() function.
| Type | Structure | Main Idea |
|---|---|---|
| Single | A → B | One parent, one child |
| Multiple | A + B → C | Multiple parents, one child |
| Multilevel | A → B → C | Inheritance across multiple levels |
| Hierarchical | A → B, C, D | Multiple children share one parent |
| Hybrid | Combination | Two or more inheritance patterns combined |
The simplest inheritance structure that accurately represents the problem is generally easier to understand.
If there is only one parent and one specialized child, single inheritance may be sufficient.
If several classes share the same parent, hierarchical inheritance may make sense.
If a class genuinely combines functionality from multiple independent classes, multiple inheritance may be useful.
If inheritance continues across several levels, multilevel inheritance can represent that hierarchy.
Complex combinations should be designed carefully because complicated inheritance structures can become difficult to maintain.
Imagine an education management system.
We could have:
Person
|
├── Student
└── Teacher
This is hierarchical inheritance.
Then:
Student
|
↓
CollegeStudent
|
↓
EngineeringStudent
This is multilevel inheritance.
If a specialized class combines functionality from two independent classes, multiple inheritance could also be used.
These structures allow a large application to model relationships between different types of objects.
mro() and __mro__ can be used to inspect the method resolution order.In the next part, we will focus on method overriding and super(). You will learn how a child class can customize inherited behavior, how to extend rather than completely replace parent methods, and how super() works in inheritance hierarchies.
In the previous parts, you learned what inheritance is and explored its major types, including single, multiple, multilevel, hierarchical, and hybrid inheritance. You also saw how a child class can reuse functionality from a parent class.
Now we will focus on two closely related concepts that make inheritance much more powerful: method overriding and super().
Method overriding allows a child class to provide its own implementation of a method inherited from the parent class. The super() function allows the child class to access functionality from its parent instead of completely replacing it.
Method overriding occurs when a child class defines a method with the same name as a method in its parent class.
Consider a simple example:
class Animal:
def speak(self):
print("Animal makes a sound")
class Dog(Animal):
def speak(self):
print("Dog barks")
The parent class contains:
speak()
The child class also defines:
speak()
Now create a dog:
dog1 = Dog()
dog1.speak()
Output:
Dog barks
Python uses the implementation defined in Dog because the child class has overridden the inherited method.
Inheritance allows a parent class to define common behavior, but different child classes may need to perform that behavior differently.
For example, all animals may have a speak() behavior, but different animals make different sounds.
class Animal:
def speak(self):
print("Animal makes a sound")
class Dog(Animal):
def speak(self):
print("Dog barks")
class Cat(Animal):
def speak(self):
print("Cat meows")
class Cow(Animal):
def speak(self):
print("Cow moos")
Now:
dog = Dog()
cat = Cat()
cow = Cow()
dog.speak()
cat.speak()
cow.speak()
Output:
Dog barks
Cat meows
Cow moos
The parent class establishes a common method name, while each child provides its own specialized implementation.
Method overriding is also common in business applications.
class Employee:
def calculate_bonus(self):
return 5000
class Manager(Employee):
def calculate_bonus(self):
return 15000
class Developer(Employee):
def calculate_bonus(self):
return 10000
Now:
manager = Manager()
developer = Developer()
print(manager.calculate_bonus())
print(developer.calculate_bonus())
Output:
15000
10000
Each child class provides its own implementation of the inherited method.
A child class can also define its own __init__() method.
class Person:
def __init__(self, name):
self.name = name
class Student(Person):
def __init__(self, name, course):
self.name = name
self.course = course
Now:
student1 = Student(
"Rahul",
"Data Analytics"
)
The child class has its own constructor, so the parent’s constructor is not automatically executed in this implementation.
The child has duplicated:
self.name = name
from the parent.
This is where super() becomes useful.
super() provides a convenient way to access methods and other functionality from a parent class or the next class in the inheritance hierarchy.
For example:
class Person:
def __init__(self, name):
self.name = name
class Student(Person):
def __init__(self, name, course):
super().__init__(name)
self.course = course
Here:
super().__init__(name)
calls the parent class’s constructor.
The parent initializes:
self.name
Then the child adds:
self.course
This produces a cleaner design because the child does not need to duplicate the parent’s initialization logic.
super() is not limited to constructors.
It can also call a parent method.
class Animal:
def speak(self):
print("Animal makes a sound")
class Dog(Animal):
def speak(self):
super().speak()
print("Dog barks")
Now:
dog1 = Dog()
dog1.speak()
Output:
Animal makes a sound
Dog barks
The child method first calls the parent implementation:
super().speak()
and then adds its own behavior.
There are two common approaches when a child class needs different behavior.
Complete overriding:
class Dog(Animal):
def speak(self):
print("Dog barks")
The parent implementation is replaced for the child.
Extending the parent method:
class Dog(Animal):
def speak(self):
super().speak()
print("Dog barks")
The parent behavior is preserved and additional child behavior is added.
This distinction is important when designing reusable classes.
Consider a company where every employee has a basic salary calculation, but managers receive an additional allowance.
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def calculate_salary(self):
return self.salary
Now create a manager:
class Manager(Employee):
def calculate_salary(self):
base_salary = super().calculate_salary()
return base_salary + 20000
Create a manager:
manager1 = Manager(
"Priya",
80000
)
Now:
print(manager1.calculate_salary())
Output:
100000
The child class reused the parent’s salary calculation and then added a manager-specific amount.
One of the most common uses of super() is inside a child constructor.
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
class Developer(Employee):
def __init__(
self,
name,
salary,
language
):
super().__init__(
name,
salary
)
self.language = language
Now:
developer1 = Developer(
"Rahul",
60000,
"Python"
)
The parent constructor handles:
name
salary
The child constructor handles:
language
This separation keeps the classes easier to maintain.
The importance of super() becomes even greater when multiple inheritance is used.
Consider:
class A:
def show(self):
print("A")
class B(A):
def show(self):
super().show()
print("B")
class C(B):
def show(self):
super().show()
print("C")
Now:
object1 = C()
object1.show()
Output:
A
B
C
Each class calls the next implementation through super().
This demonstrates why super() is more than simply a shortcut for directly naming the parent class. In more advanced inheritance structures, it works with Python’s method resolution order.
Python uses Method Resolution Order (MRO) to determine the order in which classes are searched.
Consider:
class A:
pass
class B(A):
pass
class C(B):
pass
You can inspect the order using:
print(C.mro())
The resulting list shows the order Python follows when looking for methods.
In multiple inheritance, this becomes particularly important because there may be more than one parent.
For example:
class A:
pass
class B(A):
pass
class C(A):
pass
class D(B, C):
pass
Python calculates an MRO for D.
You can inspect it using:
print(D.mro())
super() follows this resolution order rather than simply meaning “call the class written immediately above me.”
Inheritance and encapsulation can work together.
class Employee:
def __init__(self, salary):
self.__salary = salary
def get_salary(self):
return self.__salary
class Manager(Employee):
def get_salary(self):
salary = super().get_salary()
return salary + 20000
Now:
manager1 = Manager(80000)
print(manager1.get_salary())
Output:
100000
The child class uses the parent’s public method to obtain the internal salary and then adds its own business rule.
This demonstrates how multiple OOP concepts can work together.
Consider a general Vehicle class:
class Vehicle:
def __init__(self, brand):
self.brand = brand
def start(self):
print(self.brand, "vehicle is starting")
Now create a car:
class Car(Vehicle):
def __init__(self, brand, model):
super().__init__(brand)
self.model = model
def start(self):
super().start()
print(
self.model,
"engine is running"
)
Create the object:
car1 = Car(
"Toyota",
"Camry"
)
Call:
car1.start()
Output:
Toyota vehicle is starting
Camry engine is running
The parent handles the common behavior, while the child adds specialized behavior.
Method overriding is appropriate when a child class needs behavior that differs from the parent implementation.
For example:
Animal.speak()
may provide general behavior, while:
Dog.speak()
Cat.speak()
Cow.speak()
provide specialized behavior.
Similarly, different employee types may calculate bonuses differently:
Employee.calculate_bonus()
Manager.calculate_bonus()
Developer.calculate_bonus()
Overriding allows the common interface to remain the same while the implementation varies between child classes.
Use super() when the child class needs to reuse functionality from the parent while adding or customizing behavior.
Typical situations include:
super() where appropriate.super() without understanding the inheritance hierarchy.super() always means only the immediate parent class.Create an Employee class with:
calculate_bonus()Then create:
DeveloperManagerOverride calculate_bonus() in both child classes.
For example:
class Employee:
def calculate_bonus(self):
return 5000
class Developer(Employee):
def calculate_bonus(self):
return 10000
class Manager(Employee):
def calculate_bonus(self):
return 20000
Then test each object.
Create:
Vehicle
↓
Car
↓
ElectricCar
Give Vehicle a start() method.
Override start() in Car.
Then override it again in ElectricCar.
Use super() so that each child can extend the behavior inherited from its parent.
super() allows a child class to reuse parent functionality.super().__init__() is commonly used to call a parent constructor.super().method() can call a parent or next-in-MRO method.super() allows a child to extend parent behavior instead of duplicating it.super() works with Python’s Method Resolution Order.You now understand how inheritance can be customized through method overriding and super(). The final part will combine inheritance concepts into practical projects and exercises, compare all inheritance types, cover common interview questions, and provide a complete revision of the lesson.
You have now learned the major concepts of Python inheritance: parent and child classes, code reuse, different types of inheritance, method overriding, super(), and Method Resolution Order. This final section brings these concepts together through a practical project, coding exercises, interview questions, and a complete revision.
Let’s build a small Employee Management System using inheritance.
Our system will have a general Employee class and specialized child classes for Developer and Manager.
The parent class will contain common employee information and behavior:
The child classes will add specialized behavior.
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def display(self):
print("Name:", self.name)
print("Salary:", self.salary)
def annual_salary(self):
return self.salary * 12
def calculate_bonus(self):
return self.salary * 0.05
Now create the Developer class:
class Developer(Employee):
def __init__(self, name, salary, language):
super().__init__(name, salary)
self.language = language
def calculate_bonus(self):
return self.salary * 0.10
def display(self):
super().display()
print(
"Programming Language:",
self.language
)
Now create the Manager class:
class Manager(Employee):
def __init__(self, name, salary, team_size):
super().__init__(name, salary)
self.team_size = team_size
def calculate_bonus(self):
return self.salary * 0.15
def display(self):
super().display()
print(
"Team Size:",
self.team_size
)
Now create objects:
developer1 = Developer(
"Rahul",
60000,
"Python"
)
manager1 = Manager(
"Priya",
90000,
8
)
Display the developer:
developer1.display()
Output:
Name: Rahul
Salary: 60000
Programming Language: Python
Display the manager:
manager1.display()
Output:
Name: Priya
Salary: 90000
Team Size: 8
Now calculate bonuses:
print(developer1.calculate_bonus())
print(manager1.calculate_bonus())
Output:
6000.0
13500.0
Notice how the same method name, calculate_bonus(), produces different results depending on the object.
This is method overriding in action.
The parent class contains functionality common to all employees.
Employee
|
├── name
├── salary
├── display()
├── annual_salary()
└── calculate_bonus()
The Developer class specializes the parent:
Developer
|
├── language
└── calculate_bonus()
The Manager class also specializes the parent:
Manager
|
├── team_size
└── calculate_bonus()
The common functionality is defined once in Employee, while specialized functionality is implemented in the child classes.
The constructors use:
super().__init__(name, salary)
so the parent class remains responsible for initializing the common employee information.
The parent defines:
def calculate_bonus(self):
return self.salary * 0.05
The developer overrides it:
def calculate_bonus(self):
return self.salary * 0.10
The manager also overrides it:
def calculate_bonus(self):
return self.salary * 0.15
Therefore:
Employee → 5%
Developer → 10%
Manager → 15%
The method name remains the same, but the implementation differs between classes.
This example also provides an introduction to another major OOP concept: polymorphism.
Suppose we create:
employees = [
Developer("Rahul", 60000, "Python"),
Manager("Priya", 90000, 8)
]
We can loop through the objects:
for employee in employees:
print(
employee.name,
employee.calculate_bonus()
)
Output:
Rahul 6000.0
Priya 13500.0
The same method call:
employee.calculate_bonus()
behaves differently depending on which object is being processed.
This is one of the fundamental ideas behind polymorphism and will become an important topic in a later OOP lesson.
Create an Animal parent class with:
nameeat()speak()Create three child classes:
DogCatCowOverride speak() in every child class.
For example:
class Dog(Animal):
def speak(self):
print("Dog barks")
Then create objects and call:
dog.speak()
cat.speak()
cow.speak()
The output should be different for each animal.
Create a parent class:
Vehicle
with:
start()Create:
CarBikeElectricCarUse inheritance to reuse common vehicle functionality.
Override start() where necessary.
Use super() when you want to retain the parent behavior and add specialized behavior.
Create a parent class:
Person
with:
display()Create:
Student(Person)
Teacher(Person)
The student should have:
The teacher should have:
Override display() in both child classes and use super() to reuse the parent’s display behavior.
Create this hierarchy:
Person
↓
Employee
↓
Manager
Person should contain:
name
Employee should add:
salary
Manager should add:
team_size
Use super() at each level to initialize the inherited data.
Create two parent classes:
class PythonSkills:
def python(self):
print("Python")
class SQLSkills:
def sql(self):
print("SQL")
Then create:
class DataAnalyst(
PythonSkills,
SQLSkills
):
pass
Create an object and call both inherited methods.
Then add a method called:
analyze_data()
to the child class.
| Inheritance Type | Structure | Example |
|---|---|---|
| Single | A → B | Animal → Dog |
| Multiple | A + B → C | Python + SQL → Data Analyst |
| Multilevel | A → B → C | Person → Employee → Manager |
| Hierarchical | A → B, C | Employee → Developer, Manager |
| Hybrid | Combination | Combination of multiple patterns |
Mistake 1: Using inheritance for a “has-a” relationship
A car has an engine. It is not an engine. Composition may therefore be more appropriate.
Mistake 2: Repeating parent code
If the child needs parent initialization, use:
super().__init__()
instead of unnecessarily duplicating the parent’s code.
Mistake 3: Forgetting method overriding
If two child classes need different behavior, overriding allows them to provide their own implementation while keeping a common method interface.
Mistake 4: Creating very deep inheritance chains
Large inheritance hierarchies can become difficult to understand and maintain.
Mistake 5: Ignoring MRO
When using multiple inheritance, understand Python’s Method Resolution Order.
You can inspect it using:
ClassName.mro()
1. What is inheritance?
Inheritance is an OOP mechanism that allows a child class to reuse and extend functionality from a parent class.
2. What is a parent class?
A parent or base class is a class whose attributes and methods can be inherited by another class.
3. What is a child class?
A child or derived class is a class that inherits from another class.
4. How do you create inheritance in Python?
class Child(Parent):
pass
5. What is method overriding?
Method overriding occurs when a child class defines a method with the same name as a method in its parent.
6. What is super()?
super() provides access to parent or next-in-MRO functionality.
7. Why is super().__init__() commonly used?
It allows a child constructor to reuse the initialization logic defined in the parent class.
8. What is multiple inheritance?
It occurs when one child class inherits from multiple parent classes.
9. What is MRO?
MRO stands for Method Resolution Order. It determines the order Python follows when searching for methods and attributes in an inheritance hierarchy.
10. What is the difference between inheritance and composition?
Inheritance generally represents an “is-a” relationship, while composition generally represents a “has-a” relationship.
The complete inheritance concept can be remembered using the following structure:
Inheritance
|
├── Parent Class
│ ↓
│ Common Features
|
└── Child Class
↓
Reuse + Extend
|
├── New Attributes
├── New Methods
├── Method Overriding
└── super()
The five major inheritance patterns are:
Single
A → B
Multiple
A + B → C
Multilevel
A → B → C
Hierarchical
A
/ \
B C
Hybrid
Combination of patterns
The most important practical rule is to use inheritance when there is a meaningful “is-a” relationship.
For example:
Dog is an Animal
Developer is an Employee
Manager is an Employee
Student is a Person
When the relationship is instead:
Car has an Engine
Student has an Address
Employee has a Department
composition may be a better design choice.
Inheritance allows you to build general classes first and then create specialized classes from them. The parent class provides reusable functionality, while child classes can add new behavior or override existing behavior.
super() is particularly useful when the child wants to reuse the parent’s implementation instead of duplicating it.
With inheritance, method overriding, and super() understood, you are now ready for the next major OOP concept: Polymorphism in Python. In polymorphism, the same interface or method call can produce different behavior depending on the object using it.