Polymorphism in Python
Polymorphism is a foundational concept in programming that allows entities like functions, methods or operators to behave differently based on the type of data they are handling. Derived from Greek, the term literally means “many forms”.
Python’s dynamic typing and duck typing make it inherently polymorphic. Functions, operators and even built-in objects like loops exhibit polymorphic behavior.
Polymorphism in Built-in Functions
Python’s built-in functions exhibit polymorphism, adapting to various data types.
Example:
print(len("Hello")) # String length
print(len([1, 2, 3])) # List length
print(max(1, 3, 2)) # Maximum of integers
print(max("a", "z", "m")) # Maximum in strings
Python determines behavior at runtime, enabling these functions to work across diverse types without explicit type declarations.
Let’s explore polymorphism in detail:
Table of Content
Polymorphism in Functions
Duck typing enables functions to work with any object regardless of its type.
Example:
def add(a, b):
return a + b
print(add(3, 4)) # Integer addition
print(add("Hello, ", "World!")) # String concatenation
print(add([1, 2], [3, 4])) # List concatenation
Polymorphism in Operators
Operator Overloading
In Python, operators like + behave polymorphically, performing addition, concatenation or merging based on the data type.
Example:
print(5 + 10) # Integer addition
print("Hello " + "World!") # String concatenation
print([1, 2] + [3, 4]) # List concatenation
Polymorphism in OOPs
In OOP, polymorphism allows methods in different classes to share the same name but perform distinct tasks. This is achieved through inheritance and interface design. Polymorphism complements other OOP principles like inheritance (sharing behavior) and encapsulation (hiding complexity) to create robust and modular applications.
Example:
class Shape:
def area(self):
return "Undefined"
class Rectangle(Shape):
def __init__(self, length, width):
self.length = length
self.width = width
def area(self):
return self.length * self.width
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius ** 2
shapes = [Rectangle(2, 3), Circle(5)]
for shape in shapes:
print(f"Area: {shape.area()}")
Explanation:
- The code showcases polymorphism using a parent class Shape and child classes Rectangle and Circle.
- Parent Class Shape: Contains a generic area method returning “Undefined”, acting as a placeholder for derived classes to override.
- Child Class Rectangle: Initializes length and width via the __init__ constructor. Overrides the area method to return the rectangle’s area as length * width.
- Child Class Circle: Initializes radius via the __init__ constructor. Overrides the area method to return the circle’s area as 3.14 * radius^2.
- Polymorphic Behavior: A list of shape objects (Rectangle and Circle) is created. A for loop iterates through the list, calling the area method on each object. The method executed is determined by the object’s type, showcasing polymorphism.
Types of Polymorphism
Compile-time Polymorphism
- Found in statically typed languages like Java or C++, where the behavior of a function or operator is resolved during the program’s compilation phase.
- Examples include method overloading and operator overloading, where multiple functions or operators can share the same name but perform different tasks based on the context.
- In Python, which is dynamically typed, compile-time polymorphism is not natively supported. Instead, Python uses techniques like dynamic typing and duck typing to achieve similar flexibility.
Runtime Polymorphism
- Occurs when the behavior of a method is determined at runtime based on the type of the object.
- In Python, this is achieved through method overriding: a child class can redefine a method from its parent class to provide its own specific implementation.
- Python’s dynamic nature allows it to excel at runtime polymorphism, enabling flexible and adaptable code.
Example:
class Animal:
def sound(self):
return "Some generic sound"
class Dog(Animal):
def sound(self):
return "Bark"
class Cat(Animal):
def sound(self):
return "Meow"
# Polymorphic behavior
animals = [Dog(), Cat(), Animal()]
for animal in animals:
print(animal.sound()) # Calls the overridden method based on the object type
Output
Bark Meow Some generic sound
Explanation: Here, the sound method behaves differently depending on whether the object is a Dog, Cat or Animal and this decision happens at runtime. This dynamic nature makes Python particularly powerful for runtime polymorphism.
Inheritance Class Polymorphism
Inheritance-based polymorphism occurs when a subclass overrides a method from its parent class, providing a specific implementation. This process of re-implementing a method in the child class is known as Method Overriding.
Example:
class Animal:
def sound(self):
return "Some generic animal sound"
class Dog(Animal):
def sound(self):
return "Bark"
class Cat(Animal):
def sound(self):
return "Meow
Explanation:
- Class Animal: Acts as the base (parent) class. Contains a method sound that provides a default behavior, returning “Some generic animal sound”. This serves as a generic representation of the sound method for all animals.
- Class Dog: Inherits from the Animal class (denoted by class Dog(Animal)). Overrides the sound method to return “Woof Woof!”, a behavior specific to dogs. This demonstrates method overriding, where the subclass modifies the implementation of the parent class’s method.
- Class Cat: Inherits from the Animal class (denoted by class Cat(Animal)). Overrides the sound method to return “Meow”, a behavior specific to cats. Like Dog, this also demonstrates method overriding.
Polymorphism in Python – FAQs
What is method overriding in polymorphism?
Method overriding occurs when a subclass provides a specific implementation of a method that is already defined in its superclass. This allows the subclass to customize or completely replace the behavior of the method inherited from the superclass.
Example:
class Animal:
def sound(self):
return "Some sound"
class Dog(Animal):
def sound(self):
return "Bark"
dog = Dog()
print(dog.sound()) # Output: "Bark"
How to achieve polymorphism using class inheritance?
Polymorphism can be achieved using class inheritance by defining a common interface in a base class and then providing specific implementations in derived classes. This allows objects of different classes to be treated as objects of a common superclass.
Example:
class Shape:
def draw(self):
raise NotImplementedError("Subclass must implement abstract method")
class Circle(Shape):
def draw(self):
return "Drawing a circle"
class Square(Shape):
def draw(self):
return "Drawing a square"
shapes = [Circle(), Square()]
for shape in shapes:
print(shape.draw())
# Output:
# Drawing a circle
# Drawing a square
Can polymorphism be achieved with functions in Python?
Yes, polymorphism can be achieved with functions in Python through duck typing, where the method or function operates on any object that supports the required method or attribute, regardless of the object’s class.
Example:
def make_sound(animal):
return animal.sound()
class Dog:
def sound(self):
return "Bark"
class Cat:
def sound(self):
return "Meow"
animals = [Dog(), Cat()]
for animal in animals:
print(make_sound(animal))
# Output:
# Bark
# Meow
How does polymorphism help in writing flexible and maintainable code?
Polymorphism allows you to write more flexible and maintainable code by enabling you to use a single interface to represent different underlying forms (data types). This means you can add new classes with their own implementations without altering the existing code, making it easier to extend and maintain.
Example:
class Vehicle:
def move(self):
raise NotImplementedError("Subclass must implement abstract method")
class Car(Vehicle):
def move(self):
return "Car is moving"
class Bike(Vehicle):
def move(self):
return "Bike is moving"
vehicles = [Car(), Bike()]
for vehicle in vehicles:
print(vehicle.move())
# Output:
# Car is moving
# Bike is moving
What is the role of abstract base classes in polymorphism?
Abstract base classes (ABCs) define a common interface for a group of subclasses. They cannot be instantiated themselves and require subclasses to provide implementations for their abstract methods. ABCs ensure that derived classes adhere to a specific protocol, thus supporting polymorphism.
Example:
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def sound(self):
pass
class Dog(Animal):
def sound(self):
return "Bark"
class Cat(Animal):
def sound(self):
return "Meow"
animals = [Dog(), Cat()]
for animal in animals:
print(animal.sound())
# Output:
# Bark
# Meow


