Python Polymorphism
Learn how different objects can respond to the same method or operation in different ways. Polymorphism is one of the most powerful ideas in object-oriented programming and helps you build flexible, reusable Python programs.
Learning Objectives
By the end of this lesson, you will understand how polymorphism allows different objects to be treated through a common interface while producing different behavior.
You will learn how to:
- Understand the meaning of polymorphism.
- Use the same method name with different classes.
- Understand method overriding.
- Use polymorphism with inheritance.
- Understand Python's duck typing approach.
- Recognize polymorphism in built-in functions.
- Use polymorphism in practical application design.
- Design flexible systems without unnecessary
if/elifchecks.
What Is Polymorphism?
The word polymorphism means "many forms."
In programming, polymorphism means that the same interface, method, function, or operation can work with different types of objects and produce behavior appropriate to each object.
For example, imagine that both a dog and a cat have a method
called speak().
Calling speak() on a dog might produce
Woof!, while calling it on a cat might produce
Meow!.
Why Polymorphism Matters
Without polymorphism, a program may need to constantly check what type of object it is working with.
For example, you could write code such as:
if animal_type == "dog":
print("Woof!")
elif animal_type == "cat":
print("Meow!")
elif animal_type == "bird":
print("Tweet!")
This approach becomes difficult to maintain when many object types are introduced.
With polymorphism, each object can provide its own implementation of a common operation.
for animal in animals:
animal.speak()
The calling code does not need to know whether
animal is a dog, cat, bird, or another compatible
object.
Same Interface, Different Behavior
One of the easiest ways to understand polymorphism is to imagine several objects that provide the same method name.
class Dog:
def speak(self):
print("Woof!")
class Cat:
def speak(self):
print("Meow!")
animals = [Dog(), Cat()]
for animal in animals:
animal.speak()
Woof!
Meow!
Notice that the loop does not contain a separate condition for dogs and cats.
It simply says:
speak().
Each object decides how that operation should behave.
Polymorphism with Functions
A function can work with different objects as long as those objects provide the operation the function needs.
class Dog:
def speak(self):
print("Woof!")
class Cat:
def speak(self):
print("Meow!")
def make_sound(animal):
animal.speak()
make_sound(Dog())
make_sound(Cat())
Woof!
Meow!
The function make_sound() does not need to know
the exact class of the object.
It only assumes that the object provides a
speak() method.
Polymorphism with Methods
Different classes can implement a method with the same name. When the method is called, Python uses the implementation belonging to the actual object.
class Car:
def move(self):
print("Car is driving.")
class Boat:
def move(self):
print("Boat is sailing.")
class Plane:
def move(self):
print("Plane is flying.")
vehicles = [Car(), Boat(), Plane()]
for vehicle in vehicles:
vehicle.move()
Car is driving.
Boat is sailing.
Plane is flying.
The same method call:
vehicle.move()
produces different behavior depending on the object.
Method Overriding and Polymorphism
Method overriding occurs when a child class provides its own implementation of a method inherited from a parent class.
class Animal:
def speak(self):
print("Some animal sound.")
class Dog(Animal):
def speak(self):
print("Woof!")
class Cat(Animal):
def speak(self):
print("Meow!")
animals = [Dog(), Cat()]
for animal in animals:
animal.speak()
Woof!
Meow!
Both objects are related to the same parent class, but each
child class provides its own version of speak().
Polymorphism with Inheritance
Inheritance and polymorphism often work together.
A parent class can define a common interface while child classes customize the behavior.
class Animal:
def speak(self):
print("Some animal sound.")
class Dog(Animal):
def speak(self):
print("Woof!")
class Cat(Animal):
def speak(self):
print("Meow!")
class Cow(Animal):
def speak(self):
print("Moo!")
animals = [
Dog(),
Cat(),
Cow()
]
for animal in animals:
animal.speak()
Woof!
Meow!
Moo!
The loop works with the parent-level concept of an Animal, while each child supplies its own implementation.
Duck Typing
Python is famous for its use of duck typing.
Duck typing focuses on what an object can do rather than what class it belongs to.
Python does not necessarily require us to check:
if type(obj) == SomeClass:
Instead, we can simply attempt to use the required operation.
Practical Duck Typing
Consider a bird and an airplane.
They are completely different types of objects, but both can
provide a fly() method.
class Bird:
def fly(self):
print("Bird is flying.")
class Airplane:
def fly(self):
print("Airplane is flying.")
def start_flying(obj):
obj.fly()
start_flying(Bird())
start_flying(Airplane())
Bird is flying.
Airplane is flying.
The function does not care whether the object is a
Bird or an Airplane.
It only requires that the object provide fly().
AttributeError may occur.
Polymorphism with Built-in Functions
You have already been using polymorphism in Python without necessarily realizing it.
Many built-in functions work with different types of objects.
For example, len() works with strings, lists,
tuples, dictionaries, sets, and many other objects.
print(len("Python"))
print(len([1, 2, 3]))
print(len({"a": 1, "b": 2}))
6
3
2
The same function, len(), behaves appropriately
for different object types.
The len() Function and Different Objects
The len() function is a useful real-world
demonstration of polymorphic behavior.
name = "Adnan"
numbers = [10, 20, 30, 40]
courses = ("Python", "SQL", "Power BI")
student = {
"name": "Adnan",
"age": 25
}
print(len(name))
print(len(numbers))
print(len(courses))
print(len(student))
5
4
3
2
The function call remains the same:
len(object)
But the result depends on the object being supplied.
Operator Polymorphism
Python operators can also behave differently depending on the types of operands.
Consider the + operator.
print(2 + 3)
print("Py" + "Learn")
print([1, 2] + [3, 4])
5
PyLearn
[1, 2, 3, 4]
The same operator produces different behavior:
- Numbers are added.
- Strings are concatenated.
- Lists are combined.
Polymorphism with Custom Classes
You can design your own classes to follow a common interface.
For example, suppose different payment classes all provide a
pay() method.
class CreditCardPayment:
def pay(self, amount):
print(f"Paid ${amount} using credit card.")
class BankTransfer:
def pay(self, amount):
print(f"Paid ${amount} using bank transfer.")
class CashOnDelivery:
def pay(self, amount):
print(f"Paid ${amount} using cash on delivery.")
payments = [
CreditCardPayment(),
BankTransfer(),
CashOnDelivery()
]
for payment in payments:
payment.pay(500)
Paid $500 using credit card.
Paid $500 using bank transfer.
Paid $500 using cash on delivery.
The processing code does not need to determine the payment type.
payment.pay(500)
Each object handles the operation according to its own implementation.
Common Method Names as Interfaces
In Python, a common method name can act like a simple interface between different objects.
For example, a reporting system might expect every report
object to provide a method called generate().
class SalesReport:
def generate(self):
print("Generating sales report...")
class InventoryReport:
def generate(self):
print("Generating inventory report...")
class CustomerReport:
def generate(self):
print("Generating customer report...")
def create_report(report):
report.generate()
create_report(SalesReport())
create_report(InventoryReport())
create_report(CustomerReport())
Generating sales report...
Generating inventory report...
Generating customer report...
This design makes it easier to add new report types later.
Real-World Examples
Polymorphism is especially useful in applications where several objects need to perform the same general task in different ways.
E-commerce Payment System
Different payment methods can expose the same
pay() method.
- Credit card
- Bank transfer
- Cash on delivery
- Digital wallet
Notification System
Different notification services can provide
send().
- SMS
- Push notification
Employee System
Different employee types could provide a common
calculate_salary() method.
Data Export System
Different exporters could implement:
export()
Possible implementations include CSV, JSON, Excel, or PDF.
Benefits and Limitations
Benefits
- Reduces repetitive conditional logic.
- Makes code easier to extend.
- Encourages reusable designs.
- Improves flexibility.
- Supports cleaner object-oriented architecture.
- Makes systems easier to maintain.
Limitations
- Too much abstraction can make code difficult to understand.
- Different classes still need compatible interfaces.
- Duck typing can produce runtime errors if assumptions are wrong.
- Overly complicated inheritance structures can become difficult to maintain.
Common Mistakes
1. Confusing Polymorphism with Inheritance
Inheritance and polymorphism are related, but they are not the same concept.
2. Assuming Objects Must Have the Same Class
Duck typing allows unrelated classes to work with the same function if they provide the required behavior.
3. Forgetting the Required Method
class Car:
def drive(self):
print("Driving")
def start(obj):
obj.fly()
start(Car())
This produces an AttributeError because
Car does not provide fly().
4. Overusing Type Checks
Constantly writing large chains of:
if type(obj) == ...
elif type(obj) == ...
elif type(obj) == ...
may indicate that a common interface or polymorphic design could be useful.
5. Making the Interface Inconsistent
If one class expects pay() while another uses
make_payment(), the calling code cannot treat
them consistently without additional logic.
Practice Exercises
Try these exercises without looking at the solution first.
Exercise 1 — Animals
Create Dog, Cat, and
Cow classes. Give each a
speak() method.
Exercise 2 — Vehicles
Create Car, Boat, and
Airplane classes. Give each a
move() method with different output.
Exercise 3 — Functions
Write a function called make_sound() that
accepts any object having a speak() method.
Exercise 4 — Duck Typing
Create two unrelated classes that both implement
run(). Write a function that calls
run().
Exercise 5 — Payments
Create three payment classes with the same
pay() method.
Exercise 6 — Reports
Create several report classes that all implement
generate().
Exercise 7 — Inheritance
Create a parent Employee class and child
classes that override work().
Exercise 8 — E-commerce
Create different delivery classes with a common
deliver() method.
Mini Challenge — Payment Processing System
Build a simple polymorphic payment processing system.
Your Mission
Design a payment system where different payment methods can be processed using the same function.
Requirements
-
Create a class called
CreditCardPayment. -
Create a class called
BankTransfer. -
Create a class called
CashOnDelivery. -
Give every class a method:
pay(amount). -
Create a function:
process_payment(payment, amount). -
The function should simply call
payment.pay(amount). - Test all three payment methods.
class CreditCardPayment:
def pay(self, amount):
print(f"Paid ${amount} using credit card.")
class BankTransfer:
def pay(self, amount):
print(f"Paid ${amount} using bank transfer.")
class CashOnDelivery:
def pay(self, amount):
print(f"Paid ${amount} using cash on delivery.")
def process_payment(payment, amount):
payment.pay(amount)
process_payment(CreditCardPayment(), 1000)
process_payment(BankTransfer(), 2500)
process_payment(CashOnDelivery(), 750)
DigitalWallet class without changing
the process_payment() function.
If your new payment class works without changing the processing function, you have successfully demonstrated polymorphism.
Quick Knowledge Check
Test your understanding before moving to the next lesson.
1. What does polymorphism mean?
2. What can different objects provide for polymorphic code?
3. Which concept allows child classes to provide their own version of a method?
4. What does duck typing focus on?
5. What happens when this code runs?
print(len("Python"))
6. Which operator demonstrates polymorphic behavior?
7. Why is polymorphism useful?
8. Which method could be used as a common interface for payment classes?
9. What error might occur if an object does not provide a required method?
10. What is a major advantage of polymorphism?
Lesson Summary
Polymorphism allows different objects to respond to the same operation in different ways.
- Polymorphism means many forms.
- Different objects can provide the same method with different behavior.
- Method overriding is a common form of polymorphism with inheritance.
- Python supports duck typing, focusing on what an object can do.
-
Built-in functions such as
len()work with many different object types. -
Operators such as
+can behave differently depending on operand types. -
Custom classes can share common interfaces such as
pay(),send(), orgenerate(). - Polymorphism reduces unnecessary type-specific conditional logic.
- Good polymorphic design makes applications easier to extend and maintain.
Ready to move forward?
Mark this lesson complete after you understand polymorphism, method overriding, duck typing, and common interfaces.