Object-Oriented Programming · Lesson 32 of 39
Inheritance and Polymorphism
Inheritance lets a new class reuse an existing one and add or change only what is different. Polymorphism lets you treat different kinds of objects the same way. Together they keep code short and flexible – the reason every framework, from Django to PyTorch, is built on them.
In this lesson
- Parent and child classes, overriding methods
- Calling the parent with
super() - Polymorphism and duck typing
- Abstract base classes, and when to prefer composition
A parent and its children
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def annual_pay(self):
return self.salary * 12
def describe(self):
return f"{self.name} earns ₹{self.annual_pay():,} a year"
class Manager(Employee): # Manager inherits from Employee
def __init__(self, name, salary, bonus):
super().__init__(name, salary) # let the parent set name and salary
self.bonus = bonus
def annual_pay(self): # override: same name, new behaviour
return super().annual_pay() + self.bonus
class Intern(Employee):
pass # inherits everything unchanged
for person in [Employee("Asha", 60000), Manager("Ravi", 90000, 150000), Intern("Kim", 15000)]:
print(person.describe())
Asha earns ₹720,000 a year Ravi earns ₹1,230,000 a year Kim earns ₹180,000 a year
The class family
Employee – name, salary, annual_pay(), describe()
- Manager(Employee)adds bonus, overrides annual_pay()
- Intern(Employee)uses everything as it is
Notice that describe() was written once in Employee, yet for Ravi it used Manager's annual_pay(). Python always looks up methods on the actual object's class first.
How Python finds a method
When you call ravi.annual_pay(), Python searches the object's class, then its parent, and so on up to object, the root of every class. This search order is the MRO (method resolution order):
class Employee: pass
class Manager(Employee): pass
print([cls.__name__ for cls in Manager.__mro__])
print(isinstance(Manager(), Employee), issubclass(Manager, Employee))
['Manager', 'Employee', 'object'] True True
super()
super() gives you the parent's version of a method. Use it to extend rather than completely replace behaviour – most often in __init__, so the parent can set up its own attributes.
Common mistake: forgetting super().__init__()
If a child defines its own __init__ and doesn't call super().__init__(...), the parent's attributes are never created. Later code fails with AttributeError: 'Manager' object has no attribute 'salary'.
Polymorphism
"Many forms": the same method call does the right thing for each type. The calling code doesn't need if statements for every kind of object:
class Shape:
def area(self):
raise NotImplementedError
class Circle(Shape):
def __init__(self, r):
self.r = r
def area(self):
return 3.14159 * self.r ** 2
class Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side ** 2
shapes = [Circle(1), Square(3), Circle(2)]
for s in shapes:
print(f"{type(s).__name__:<7}{s.area():8.2f}")
print("Total:", round(sum(s.area() for s in shapes), 2))
Circle 3.14 Square 9.00 Circle 12.57 Total: 24.71
Duck typing
Python doesn't even require a shared parent class. If an object has the method you call, it works – "if it walks like a duck and quacks like a duck, it's a duck":
class EmailAlert:
def send(self, msg):
return f"Email: {msg}"
class SmsAlert:
def send(self, msg):
return f"SMS: {msg[:20]}"
def notify_all(channels, message):
for channel in channels:
print(channel.send(message))
notify_all([EmailAlert(), SmsAlert()], "Your order #4512 has been shipped today")
Email: Your order #4512 has been shipped today SMS: Your order #4512 has
Abstract base classes
To force every child to implement certain methods, use the abc module. A class with an @abstractmethod cannot be instantiated until a child provides the method:
from abc import ABC, abstractmethod
class PaymentMethod(ABC):
@abstractmethod
def pay(self, amount): ...
class Upi(PaymentMethod):
def pay(self, amount):
return f"Paid ₹{amount} via UPI"
print(Upi().pay(250))
try:
PaymentMethod()
except TypeError as error:
print("TypeError:", error)
Paid ₹250 via UPI TypeError: Can't instantiate abstract class PaymentMethod without an implementation for abstract method 'pay'
Composition: "has a" instead of "is a"
Inheritance models an is-a relationship (a Manager is an Employee). When one object merely uses another (a Car has an Engine), store it as an attribute instead. Experienced developers prefer composition whenever inheritance doesn't clearly fit – it keeps classes small and easy to change.
class Engine:
def __init__(self, hp):
self.hp = hp
def start(self):
return f"{self.hp} hp engine started"
class Car:
def __init__(self, model, engine):
self.model = model
self.engine = engine # a Car HAS an Engine
def drive(self):
return f"{self.model}: {self.engine.start()}"
print(Car("Nexon EV", Engine(143)).drive())
Nexon EV: 143 hp engine started
Practice
Try it yourself
Create
Animalwith aspeak()method and two children,DogandCat, that override it. Loop over a list of animals and callspeak().Show solution
class Animal: def __init__(self, name): self.name = name def speak(self): return "..." class Dog(Animal): def speak(self): return f"{self.name} says Woof" class Cat(Animal): def speak(self): return f"{self.name} says Meow" for pet in [Dog("Bruno"), Cat("Misty")]: print(pet.speak())Bruno says Woof Misty says Meow
Make
SavingsAccountinherit from a simpleAccount(owner, balance)and add anadd_interest(rate)method.Show solution
class Account: def __init__(self, owner, balance): self.owner = owner self.balance = balance class SavingsAccount(Account): def add_interest(self, rate): self.balance += self.balance * rate / 100 return self.balance s = SavingsAccount("Neel", 10000) print(s.add_interest(7))10700.0
Quick check
What does super().__init__(...) do?
It runs the parent class's initialiser so the parent's attributes are set up.
What is method overriding?
A child class defines a method with the same name as the parent's, replacing it for objects of the child class.
Is-a or has-a: should Order inherit from Customer?
No. An order has a customer – use composition (self.customer = customer).
Summary
class Child(Parent):inherits attributes and methods; override to change behaviour.super()calls the parent's version – always in a child's__init__.- Polymorphism and duck typing let one piece of code work with many types.
- Use
ABCto enforce methods; prefer composition for has-a relationships.
Finished this lesson?
Mark it complete to track your progress, then carry on with the next lesson.