Python Polymorphism Explained for Beginners | OOP Complete Guide
Python Polymorphism – One Name, Many Forms Explained for Beginners
You already know how to build classes and use inheritance to share code between them. Now here is a question — what if ten different classes all need a method called area()? Every shape calculates area differently, but you want to call area() the same way on all of them without caring which shape it is. That is exactly what Polymorphism solves. The word comes from Greek — poly means many, morph means form. Same method name, different behaviour depending on which object uses it. This guide breaks it down completely with clear examples and real-life reasoning.
📋 Table of Contents
- What Exactly Is Polymorphism?
- Real-Life Analogy — The Same Switch, Different Lights
- Polymorphism With Functions (Built-in Examples)
- Polymorphism With Classes — Method Overriding
- Step-by-Step Trace — How Python Picks the Right Method
- Polymorphism With a Common Interface
- Duck Typing — Python's Unique Approach
- Operator Overloading — Same Operator, Different Behaviour
- Abstract Classes — Enforcing Polymorphism
- Polymorphism vs Inheritance — Key Differences
- Real-Life Project
- Practice Problems
- FAQ
- Summary
1. What Exactly Is Polymorphism?
Here is the core idea in one sentence — polymorphism lets you call the same method name on different objects and get different results based on which object is actually doing the work.
Think about a remote control. You press the power button and it turns on the TV. Your friend presses the same power button on a different remote and it turns on an AC. Same button name — power — completely different actions depending on which device is receiving the signal.
In Python, polymorphism shows up in three main ways — through method overriding in inherited classes, through duck typing, and through operator overloading. You will see all three clearly in this post.
Without Polymorphism
You call circle_area(), rectangle_area(), triangle_area() separately for each shape. More functions, more names to remember, harder to loop through mixed shapes.
With Polymorphism
You call area() on any shape — Python figures out which version to run. One loop handles every shape. Code stays clean no matter how many shapes you add.
2. Real-Life Analogy — The Same Switch, Different Lights
💡 One Action, Many Reactions
In your house, you have a single habit — flick the switch upward to turn on the light. In the bedroom, flicking up turns on a warm dim light. In the kitchen, the same action turns on bright white light. In the bathroom, it turns on both light and exhaust fan together.
You performed one action — flick switch — but each room responded differently based on what was installed there. That is polymorphism. In Python, you call one method name, and each object responds in its own way based on how it was defined.
3. Polymorphism With Functions — Built-in Examples
You have already used polymorphism without knowing it. Python's built-in functions like len(), +, and print() are polymorphic — they behave differently depending on what type of data you pass them.
# len() behaves differently for each type print(len("Python")) # String — counts characters print(len([1, 2, 3, 4])) # List — counts items print(len({"a": 1, "b": 2})) # Dict — counts keys print(len((10, 20, 30))) # Tuple — counts items
6 4 2 3
# + operator is also polymorphic print(10 + 20) # Addition for integers print(3.5 + 1.5) # Addition for floats print("Hello" + " World") # Concatenation for strings print([1, 2] + [3, 4]) # Merge for lists
30 5.0 Hello World [1, 2, 3, 4]
+ operator, four completely different behaviours — all based on what type of data is on each side. This is polymorphism at the language level, and it is why Python feels so natural to write.4. Polymorphism With Classes — Method Overriding
When you have multiple classes with the same method name — and each class has its own version of that method — calling the method gives a different result depending on which object you called it on. This is the most common form of polymorphism in OOP.
class Circle: def __init__(self, radius): self.radius = radius def area(self): return 3.14159 * self.radius ** 2 def describe(self): print(f"Circle with radius {self.radius}") class Rectangle: def __init__(self, length, width): self.length = length self.width = width def area(self): return self.length * self.width def describe(self): print(f"Rectangle {self.length}x{self.width}") class Triangle: def __init__(self, base, height): self.base = base self.height = height def area(self): return 0.5 * self.base * self.height def describe(self): print(f"Triangle base={self.base} height={self.height}") # Polymorphism — same call, different result shapes = [Circle(7), Rectangle(5, 4), Triangle(6, 8)] for shape in shapes: shape.describe() print(f"Area: {shape.area():.2f}\n")
Circle with radius 7 Area: 153.94 Rectangle 5x4 Area: 20.00 Triangle base=6 height=8 Area: 24.00
.area() on whatever object it finds — and Python handles the rest. This is the power of polymorphism.5. Step-by-Step Trace — How Python Picks the Right Method
6. Polymorphism With a Common Interface (Using Inheritance)
A very clean way to guarantee polymorphism is to have all classes inherit from one common parent. The parent defines what methods must exist. Each child defines how they work.
class Animal: def __init__(self, name): self.name = name def speak(self): raise NotImplementedError("Every animal must define speak()") def move(self): raise NotImplementedError("Every animal must define move()") class Dog(Animal): def speak(self): print(f"{self.name} says: Woof Woof!") def move(self): print(f"{self.name} runs on four legs.") class Bird(Animal): def speak(self): print(f"{self.name} says: Tweet Tweet!") def move(self): print(f"{self.name} flies through the air.") class Fish(Animal): def speak(self): print(f"{self.name} is silent — fish don't speak!") def move(self): print(f"{self.name} swims through the water.") # Polymorphism — all animals, one loop animals = [Dog("Bruno"), Bird("Tweety"), Fish("Nemo")] for animal in animals: animal.speak() animal.move() print()
Bruno says: Woof Woof! Bruno runs on four legs. Tweety says: Tweet Tweet! Tweety flies through the air. Nemo is silent — fish don't speak! Nemo swims through the water.
7. Duck Typing — Python's Unique Approach to Polymorphism
Python has a famous saying — "If it walks like a duck and quacks like a duck, it's a duck." This means Python does not care what type an object actually is. It only cares whether the object has the method you are trying to call. If it does — great, it works. If not — you get an error.
This is different from languages like Java where you need to formally declare that a class implements an interface. Python trusts you to pass the right objects.
# These three classes have NO inheritance relationship class Printer: def produce(self): print("Printer: Printing a document...") class Camera: def produce(self): print("Camera: Capturing a photograph...") class MusicPlayer: def produce(self): print("Music Player: Playing a song...") # Duck typing — Python only checks if produce() exists def activate(device): device.produce() # Works on ANY object with produce() devices = [Printer(), Camera(), MusicPlayer()] for d in devices: activate(d)
Printer: Printing a document... Camera: Capturing a photograph... Music Player: Playing a song...
produce() method, the activate() function works perfectly on all of them. This flexibility is what makes Python so expressive.8. Operator Overloading — Same Operator, Different Behaviour
You can teach Python what to do when standard operators like +, -, *, ==, and > are used on your custom objects. This is done through special dunder methods (double underscore methods).
class Vector: """Represents a 2D mathematical vector.""" def __init__(self, x, y): self.x = x self.y = y def __add__(self, other): # What happens when you use + between two Vectors return Vector(self.x + other.x, self.y + other.y) def __sub__(self, other): return Vector(self.x - other.x, self.y - other.y) def __mul__(self, scalar): # Multiply a vector by a number return Vector(self.x * scalar, self.y * scalar) def __eq__(self, other): return self.x == other.x and self.y == other.y def __str__(self): return f"Vector({self.x}, {self.y})" v1 = Vector(3, 4) v2 = Vector(1, 2) print(v1 + v2) # calls __add__ print(v1 - v2) # calls __sub__ print(v1 * 3) # calls __mul__ print(v1 == v2) # calls __eq__ print(v1 == Vector(3, 4)) # True
Vector(4, 6) Vector(2, 2) Vector(9, 12) False True
Most Useful Dunder Methods for Operator Overloading
| Dunder Method | Operator | What it enables |
|---|---|---|
__add__(self, other) | + | Addition between two objects |
__sub__(self, other) | - | Subtraction between two objects |
__mul__(self, other) | * | Multiplication |
__eq__(self, other) | == | Equality check |
__lt__(self, other) | < | Less than comparison |
__gt__(self, other) | > | Greater than comparison |
__len__(self) | len() | Length of the object |
__str__(self) | print() | String representation |
9. Abstract Classes — Enforcing Polymorphism
What if you want to guarantee that every child class implements a specific method? Without any enforcement, a developer might forget to add area() to a new Shape class — and the bug only shows up at runtime.
Abstract classes solve this. An abstract class declares a method but intentionally leaves it empty. Any child that inherits from it must implement that method — or Python raises an error the moment you try to create the object.
from abc import ABC, abstractmethod class Shape(ABC): # Abstract base class @abstractmethod def area(self): pass # No implementation — must be done in child @abstractmethod def perimeter(self): pass class Circle(Shape): def __init__(self, r): self.r = r def area(self): return 3.14159 * self.r ** 2 def perimeter(self): return 2 * 3.14159 * self.r class Square(Shape): def __init__(self, s): self.s = s def area(self): return self.s ** 2 def perimeter(self): return 4 * self.s shapes = [Circle(5), Square(4)] for s in shapes: print(f"Area: {s.area():.2f} | Perimeter: {s.perimeter():.2f}")
Area: 78.54 | Perimeter: 31.42 Area: 16.00 | Perimeter: 16.00
# Try to create abstract class directly — Python refuses s = Shape()
TypeError: Can't instantiate abstract class Shape with abstract methods area, perimeter
10. Polymorphism vs Inheritance — Key Differences
| Feature | Inheritance | Polymorphism |
|---|---|---|
| Core idea | Child reuses parent's code | Same method name, different behaviour |
| Requires parent class? | Yes — always | Not always — duck typing needs no parent |
| Main keyword | class Child(Parent) | Same method name in multiple classes |
| Focus | Code reuse and hierarchy | Flexibility and uniform interface |
| Used for | Sharing common behaviour | Treating different objects the same way |
| Works without the other? | Yes | Yes — duck typing is independent |
11. Real-Life Project — Payment System
from abc import ABC, abstractmethod class Payment(ABC): """Abstract base for all payment methods.""" def __init__(self, amount): self.amount = amount @abstractmethod def process(self): pass @abstractmethod def confirm(self): pass def receipt(self): print(f"\n--- Receipt ---") print(f"Amount: ₹{self.amount}") self.confirm() class UPIPayment(Payment): def __init__(self, amount, upi_id): super().__init__(amount) self.upi_id = upi_id def process(self): print(f"Processing UPI payment to {self.upi_id}...") def confirm(self): print(f"Method: UPI → {self.upi_id}") print(f"Status: ✓ Success") class CardPayment(Payment): def __init__(self, amount, card_last4): super().__init__(amount) self.card_last4 = card_last4 def process(self): print(f"Processing Card payment ending in {self.card_last4}...") def confirm(self): print(f"Method: Card ****{self.card_last4}") print(f"Status: ✓ Approved") class CODPayment(Payment): def process(self): print(f"Order placed for Cash on Delivery...") def confirm(self): print(f"Method: Cash on Delivery") print(f"Status: ✓ Order Confirmed") # Polymorphism — same interface for all payment types payments = [ UPIPayment(1500, "rohan@upi"), CardPayment(3200, "5678"), CODPayment(800) ] for pay in payments: pay.process() pay.receipt()
Processing UPI payment to rohan@upi... --- Receipt --- Amount: ₹1500 Method: UPI → rohan@upi Status: ✓ Success Processing Card payment ending in 5678... --- Receipt --- Amount: ₹3200 Method: Card ****5678 Status: ✓ Approved Order placed for Cash on Delivery... --- Receipt --- Amount: ₹800 Method: Cash on Delivery Status: ✓ Order Confirmed
12. Practice Problems
Beginner level
- Create Dog, Cat, Cow classes each with a sound() method. Loop through them polymorphically.
- Create Circle and Square with area() and perimeter() methods.
- Demonstrate len() working differently on string, list, and dict.
- Show how + works differently for int, float, string, and list.
- Create 3 unrelated classes with a greet() method and call them via duck typing.
Intermediate level
- Overload the + operator in a BankAccount class to merge two accounts.
- Overload == in a Student class to compare by marks.
- Use an abstract class to enforce that all shapes must have area() and perimeter().
- Create a media player with Play class. Add Video, Audio, Podcast children — each with a play() method.
- Overload __str__ in 3 different classes and observe how print() behaves differently.
Real-life projects
- Build a Notification system — Email, SMS, Push — all with a send() method.
- Build a Vehicle system — Car, Bike, Truck — each with fuel_cost() method.
- Create a Logger — FileLogger, ConsoleLogger, CloudLogger with log() method.
- Build Employee types — Permanent, Contract, Intern — each with salary() method.
- Create a payment gateway with UPI, Card, Wallet, COD using abstract class.
Think deeper
- Is polymorphism possible without inheritance in Python?
- What is the difference between duck typing and formal interfaces?
- What happens if a child class does not implement an abstract method?
- Can you overload the > operator to sort custom objects?
- How does Python use polymorphism internally with str(), list(), int()?
13. FAQ
Q1. What is the simplest way to explain polymorphism to a beginner?
Polymorphism means one name, many forms. You call the same method — like area() — on different objects and each one responds based on its own logic. A Circle calculates area one way. A Rectangle does it another way. Same call, different results. Python automatically picks the right version based on which object you called it on.
Q2. Is duck typing the same as polymorphism?
Duck typing is Python's way of achieving polymorphism without requiring formal inheritance. In duck typing, Python only checks whether an object has the method being called — it does not care about the object's class or type. So yes, duck typing is a form of polymorphism, but it is more flexible because it requires no class hierarchy at all.
Q3. What happens if I call a method that a child class did not implement?
Python searches up the MRO — if the parent has the method, Python uses the parent's version. If neither the child nor any parent has it, Python raises an AttributeError. If you use abstract classes, Python raises a TypeError the moment you try to create the object — before you even call the method.
Q4. What is the difference between method overriding and method overloading?
Method overriding happens in inheritance — a child class redefines a method from its parent. Method overloading means having multiple methods with the same name but different parameters — this exists in Java and C++ but Python handles it differently using default arguments and *args instead.
Q5. What is the abc module and why do I need it?
abc stands for Abstract Base Classes. The abc module provides the ABC class and the @abstractmethod decorator. Together they let you define abstract classes — classes that cannot be instantiated directly and that force all child classes to implement specific methods. It is the professional way to enforce a consistent interface across all your subclasses.
Q6. Can I use polymorphism without any OOP at all?
Yes — Python's built-in functions are the best example. len(), print(), and str() work on completely different data types polymorphically. Even the + operator works polymorphically on integers, floats, strings, and lists — all without you defining a single class. Polymorphism is a broader programming concept, not just an OOP feature.
✅ Quick Summary — What You Learned
- Polymorphism means the same method name behaves differently depending on which object calls it
- Python's built-in operators and functions like
+,len()are already polymorphic - Method overriding in inherited classes is the most common form of polymorphism
- Duck typing — Python checks if the method exists, not what type the object is
- Operator overloading uses dunder methods like
__add__,__eq__,__str__ - Abstract classes (using abc module) enforce that child classes implement required methods
- You cannot create an abstract class directly — it exists only to be inherited
- Polymorphism makes loops that work on mixed object types clean and scalable
"Polymorphism is Python's way of saying — I don't care who you are. If you can do the job, you're hired."
— Code with PyFound this post useful? Share it with a classmate learning OOP. Try the payment project — it ties everything together perfectly!
Comments
Post a Comment