Python tutorials  /  Polymorphism in Python
Chapter 15 · Python

Polymorphism in Python

Polymorphism is an Object-Oriented Programming concept that allows objects of different classes to be treated as objects of a common parent type, while each object responds to the same method call in its own specific way. The word 'polymorphism' comes from Greek, meaning 'many forms'.

Polymorphism simply means 'one interface, many implementations'. In Python, it means you can call the same method name on different objects, and each object will behave differently based on its own class definition. You don't need to know the exact type of the object to use it correctly; you just call the method and Python figures out which version to run.

Consider a remote control with a 'power' button. Whether you point it at a TV, an air conditioner, or a music system, pressing the same button (same interface) produces a different result for each device (different implementation). Similarly, in a food delivery app, when you click 'Prepare Order', a Restaurant object, a CloudKitchen object, and a HomeChef object all implement 'prepare_order()' differently, but the app calls the same method name on all of them without worrying about internal logic. In Python code, this is modeled by defining a common method name like 'make_sound()' across different animal classes (Dog, Cat, Cow), where each class provides its own implementation, but the calling code stays uniform.

Polymorphism allows you to write flexible and reusable code that works with objects of different types through a common interface. Without polymorphism, you would need separate conditional logic (if-else or switch statements) to handle every different object type, making code harder to maintain and extend. Polymorphism reduces code duplication, supports the Open/Closed Principle (open for extension, closed for modification), and makes it easy to add new classes without changing existing calling code.

  • Duck Typing Polymorphism: Python follows 'duck typing', meaning the type or class of an object is less important than the methods it defines. If an object has a method that is being called, Python will execute it regardless of the object's actual class, as long as the method exists ("If it walks like a duck and quacks like a duck, it's a duck").
  • Method Overriding (Runtime Polymorphism): A child class provides its own specific implementation of a method that is already defined in its parent class. When the method is called on a child class object, Python executes the child's version instead of the parent's, enabling different behavior at runtime.
  • Operator Overloading: Python allows the same operator (like +, -, ==, or len()) to behave differently depending on the operands' data types by defining special dunder methods such as __add__, __sub__, __eq__, and __len__ inside a class.
  • Polymorphism with Functions and Objects: A single function can accept objects of different classes as arguments and call the same method on them, producing different results depending on the object's class, without checking the object's type explicitly.
  • Polymorphism with Abstract Base Classes: Using Python's 'abc' module, you can define abstract methods in a base class that must be implemented by every subclass, enforcing a consistent interface while still allowing different behaviors.
class ParentClass: def method_name(self): pass class ChildClass1(ParentClass): def method_name(self): # custom implementation 1 pass class ChildClass2(ParentClass): def method_name(self): # custom implementation 2 pass for obj in [ChildClass1(), ChildClass2()]: obj.method_name() # Same call, different behavior
Suppose you are building a payment processing system for an e-commerce platform that supports multiple payment methods: Credit Card, UPI, and PayPal. Each payment method has a different way of processing a transaction internally (validating card numbers, verifying UPI IDs, or calling PayPal's API), but you want your checkout code to call a single, uniform method like 'process_payment()' on any payment object, without writing separate if-else blocks to check the payment type every time. Polymorphism solves this by letting each payment class define its own 'process_payment()' method while the checkout logic remains the same for all payment types.
Basic Polymorphism with Method Overriding
Demonstrates how different animal classes override a common method to produce different outputs when called through the same interface.
Python
class Animal: def make_sound(self): return "Some generic sound" class Dog(Animal): def make_sound(self): return "Bark" class Cat(Animal): def make_sound(self): return "Meow" class Cow(Animal): def make_sound(self): return "Moo" animals = [Dog(), Cat(), Cow()] for animal in animals: print(f"{animal.__class__.__name__} says: {animal.make_sound()}")
Dog says: Bark Cat says: Meow Cow says: Moo
Each subclass (Dog, Cat, Cow) overrides the make_sound() method inherited from Animal. When the loop calls animal.make_sound(), Python automatically executes the correct version based on the object's actual class, showcasing runtime polymorphism.
Polymorphism Using Duck Typing
Shows how Python does not require a common parent class for polymorphism to work, as long as the objects share the same method name.
Python
class Payment: def process_payment(self, amount): print(f"Processing card payment of Rs.{amount}") class UPI: def process_payment(self, amount): print(f"Processing UPI payment of Rs.{amount}") class PayPal: def process_payment(self, amount): print(f"Processing PayPal payment of Rs.{amount}") def checkout(payment_method, amount): payment_method.process_payment(amount) checkout(Payment(), 500) checkout(UPI(), 750) checkout(PayPal(), 1200)
Processing card payment of Rs.500 Processing UPI payment of Rs.750 Processing PayPal payment of Rs.1200
The checkout() function does not check the type of payment_method. It simply calls process_payment() on whatever object it receives. Since Payment, UPI, and PayPal classes are unrelated but share the same method name, this is duck typing in action.
Operator Overloading Polymorphism
Demonstrates how the '+' operator behaves differently for a custom class by overriding the __add__ dunder method.
Python
class Vector: def __init__(self, x, y): self.x = x self.y = y def __add__(self, other): return Vector(self.x + other.x, self.y + other.y) def __repr__(self): return f"Vector({self.x}, {self.y})" v1 = Vector(2, 3) v2 = Vector(4, 1) result = v1 + v2 print(result)
Vector(6, 4)
The '+' operator normally adds numbers, but by defining __add__ inside the Vector class, we give '+' new behavior specific to Vector objects. This is operator overloading, a form of polymorphism.
Polymorphism with Abstract Base Class
Enforces a consistent interface across subclasses using Python's abc module while allowing each subclass to implement the method differently.
Python
from abc import ABC, abstractmethod class Shape(ABC): @abstractmethod def area(self): pass class Rectangle(Shape): def __init__(self, width, height): self.width = width self.height = height def area(self): return self.width * self.height class Circle(Shape): def __init__(self, radius): self.radius = radius def area(self): return 3.14 * self.radius * self.radius shapes = [Rectangle(4, 5), Circle(3)] for shape in shapes: print(f"{shape.__class__.__name__} area: {shape.area()}")
Rectangle area: 20 Circle area: 28.26
Shape is an abstract base class that forces every subclass to implement the area() method. Rectangle and Circle each provide their own version, and calling area() on each object produces different, class-specific results while relying on the same method name.
  • Confusing Polymorphism with Method Overloading: Many beginners coming from Java or C++ expect Python to support traditional method overloading (multiple methods with the same name but different parameters). Python does not support this natively; defining a method twice simply overwrites the first definition. Developers mistakenly think this is polymorphism, when Python actually achieves similar flexibility using default arguments, *args, **kwargs, or the @singledispatch decorator.
  • Forgetting to Call super().__init__() in Overridden Methods: When overriding the __init__() method in a child class, developers often forget to call super().__init__() to initialize the parent class's attributes, which can cause AttributeError exceptions later when the parent's attributes are accessed but were never set.
  • Not Using a Common Interface or Abstract Base Class: Skipping the definition of a common interface (like an abstract method) can lead to inconsistent method names across classes, causing AttributeError at runtime when a class in a polymorphic collection doesn't implement the expected method.
  • Assuming isinstance() Checks Are Required for Polymorphism: Developers new to Python's duck typing often add unnecessary isinstance() checks or if-elif chains based on object type, defeating the purpose of polymorphism. This makes code rigid and harder to extend since every new class requires modifying the conditional logic.
  • Overriding Special Methods Incorrectly: When overloading operators like __eq__ or __add__, forgetting to return NotImplemented for unsupported types (instead of raising an error directly) can break Python's built-in fallback mechanism for comparisons and arithmetic between different classes.
  • Use Abstract Base Classes for Enforced Interfaces: When designing a system with multiple related classes (like different payment gateways or shapes), use Python's 'abc' module to define abstract methods. This ensures every subclass implements the required methods, catching errors early instead of at runtime.
  • Favor Duck Typing for Flexible, Loosely-Coupled Code: Instead of checking object types explicitly with isinstance(), write functions that simply call the expected method. This keeps your code open to extension, letting you add new classes without modifying existing polymorphic functions.
  • Keep Method Signatures Consistent Across Overrides: When overriding a method in a subclass, keep the parameter names and expected behavior consistent with the parent class to avoid confusing bugs when the object is used polymorphically.
  • Use super() to Extend, Not Replace, Parent Behavior: When overriding a method but still needing the parent class's logic, call super().method_name() inside the overridden method instead of duplicating the parent's code. This keeps your code DRY (Don't Repeat Yourself) and easier to maintain.
  • Document Expected Interfaces Clearly: Since Python doesn't enforce strict typing, add clear docstrings or type hints (using typing.Protocol) describing what methods and attributes a class must have to work correctly in a polymorphic context, improving code readability for other developers.
What is polymorphism in Python, and how is it different from method overloading?
Polymorphism means 'many forms' — it allows objects of different classes to respond to the same method call in their own specific way, typically achieved through method overriding, duck typing, or operator overloading. Method overloading, in contrast, refers to defining multiple methods with the same name but different parameters in the same class; Python does not support true method overloading like Java or C++, since a later method definition simply overwrites an earlier one with the same name.
What is duck typing, and how does it relate to polymorphism in Python?
Duck typing is a concept where Python checks for the presence of a method or attribute, rather than the actual type of the object, before executing code ("If it walks like a duck and quacks like a duck, it's a duck"). This enables polymorphism without requiring inheritance from a common base class — any object with the expected method can be used interchangeably in the same code.
How does Python achieve operator overloading, and why is it considered polymorphism?
Python achieves operator overloading by defining special dunder (double underscore) methods inside a class, such as __add__ for '+', __eq__ for '==', or __len__ for len(). This is polymorphism because the same operator behaves differently depending on the class of the operands, giving 'many forms' to a single operator symbol.
What is the role of Abstract Base Classes (ABC) in implementing polymorphism?
Abstract Base Classes, provided by Python's 'abc' module, let you define methods that must be implemented by any subclass using the @abstractmethod decorator. This enforces a consistent interface across all subclasses used polymorphically, and Python will raise a TypeError if a subclass is instantiated without implementing all abstract methods.
Can polymorphism be achieved without inheritance in Python? Explain with an example.
Yes. Because Python relies on duck typing, polymorphism does not require a shared parent class. As long as different classes define a method with the same name and signature, they can be used interchangeably in the same function or loop. For example, two unrelated classes 'UPI' and 'PayPal' can both implement 'process_payment()', and a single checkout() function can call this method on either object without any common base class.
What happens if a subclass does not override an abstract method defined in its Abstract Base Class?
If a subclass inherits from an Abstract Base Class but fails to implement all methods decorated with @abstractmethod, Python raises a TypeError at the moment you try to instantiate that subclass, stating that the class cannot be instantiated with abstract methods still pending implementation.
Create a base class 'Employee' with a method 'calculate_salary()'. Create two subclasses, 'FullTimeEmployee' and 'Freelancer', that override 'calculate_salary()' differently (fixed monthly salary vs. hourly rate * hours worked). Write a loop that calculates and prints the salary for a list of mixed employee objects.
class Employee: def calculate_salary(self): return 0 class FullTimeEmployee(Employee): def __init__(self, monthly_salary): self.monthly_salary = monthly_salary def calculate_salary(self): return self.monthly_salary class Freelancer(Employee): def __init__(self, hourly_rate, hours_worked): self.hourly_rate = hourly_rate self.hours_worked = hours_worked def calculate_salary(self): return self.hourly_rate * self.hours_worked employees = [FullTimeEmployee(50000), Freelancer(500, 40)] for emp in employees: print(f"{emp.__class__.__name__} salary: {emp.calculate_salary()}")
Write a Python program demonstrating operator overloading by creating a class 'Money' that overrides the '+' operator to add two Money objects and the '__str__' method to print the result in currency format.
class Money: def __init__(self, amount): self.amount = amount def __add__(self, other): return Money(self.amount + other.amount) def __str__(self): return f"Rs.{self.amount}" m1 = Money(100) m2 = Money(250) print(m1 + m2)
Design an abstract class 'Notification' with an abstract method 'send()'. Implement 'EmailNotification' and 'SMSNotification' subclasses. Write a function that accepts a list of notification objects and sends each one using the same interface.
from abc import ABC, abstractmethod class Notification(ABC): @abstractmethod def send(self): pass class EmailNotification(Notification): def send(self): print("Sending Email Notification") class SMSNotification(Notification): def send(self): print("Sending SMS Notification") def notify_all(notifications): for n in notifications: n.send() notify_all([EmailNotification(), SMSNotification()])

Polymorphism in Python allows objects of different classes to be used through a common interface, with each object responding to the same method call in its own way. It can be achieved through method overriding, duck typing, operator overloading, or Abstract Base Classes. This concept reduces code duplication, increases flexibility, and makes systems easier to extend, since new classes can be added without modifying existing calling code, as long as they follow the expected interface.

© 2026 CompileX. Maintained by Aditya Kumar Sharma.