Python Polymorphism
Polymorphism Fundamentals
Polymorphism lets different object types expose the same interface and be used interchangeably. In Python this is most often achieved through method overriding and duck typing—code calls the same method name on different objects and each object provides its own behavior.
# Basic polymorphism example
class Cat:
def speak(self):
return "Meow!"
class Dog:
def speak(self):
return "Woof!"
class Duck:
def speak(self):
return "Quack!"
# Polymorphic function
def animal_speak(animal):
return animal.speak()
# Creating objects
animals = [Cat(), Dog(), Duck()]
# Same interface, different behaviors
for animal in animals:
print(animal_speak(animal))
Meow! Woof! Quack!
Types of Polymorphism
Type | Description | Example |
---|---|---|
Method Overriding | Subclass provides its own implementation of a method defined in a superclass | def speak() in Dog vs Animal |
Method Overloading | Same method name with different parameter lists (not built-in for methods in Python) | Emulate with defaults, *args/**kwargs, or functools.singledispatch (for functions) |
Operator Overloading | Operators have type-specific meaning via special methods | + for numbers vs strings; __add__ in custom classes |
Duck Typing | Behavior is accepted if required methods/attributes exist (type is secondary) | If it has .speak(), treat it like an Animal |
Duck Typing
Python emphasizes capabilities over concrete types. If an object supplies the needed method, it can participate. This keeps code flexible and testable.
# Duck typing example
class Car:
def drive(self):
return "Car is driving"
class Boat:
def drive(self):
return "Boat is sailing"
class Airplane:
def drive(self):
return "Airplane is flying"
# Function that works with any object that has drive() method
def start_journey(vehicle):
return vehicle.drive()
# Different objects with same method
vehicles = [Car(), Boat(), Airplane()]
for vehicle in vehicles:
print(start_journey(vehicle))
Car is driving Boat is sailing Airplane is flying
Polymorphism with Abstract Base Classes
Abstract Base Classes (ABCs) define required methods to formalize an interface. Subclasses must implement the abstract methods, enabling consistent polymorphic use.
# Polymorphism with ABCs
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
"""Return the area of the shape."""
pass
@abstractmethod
def perimeter(self):
"""Return the perimeter of the shape."""
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
def perimeter(self):
return 2 * 3.14159 * self.radius
# Polymorphic function
def print_shape_info(shape: Shape):
print(f"Area: {shape.area():.2f}")
print(f"Perimeter: {shape.perimeter():.2f}")
print("---")
# Using different shapes
shapes = [Rectangle(5, 3), Circle(4)]
for shape in shapes:
print_shape_info(shape)
Area: 15.00 Perimeter: 16.00 --- Area: 50.27 Perimeter: 25.13 ---
Operator Overloading
Operators dispatch to special methods (dunder methods) like __add__, __mul__, and __eq__. Implementing these lets custom classes interact naturally with Python’s operators.
# Operator overloading example
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
# Overload + operator
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
# Overload * operator (scalar on the right)
def __mul__(self, scalar):
return Vector(self.x * scalar, self.y * scalar)
# Support scalar * Vector by mirroring
__rmul__ = __mul__
# Overload == operator
def __eq__(self, other):
return (self.x, self.y) == (other.x, other.y)
def __str__(self):
return f"({self.x}, {self.y})"
# Using overloaded operators
v1 = Vector(2, 3)
v2 = Vector(1, 4)
print(f"v1: {v1}")
print(f"v2: {v2}")
print(f"v1 + v2: {v1 + v2}")
print(f"v1 * 3: {v1 * 3}")
print(f"3 * v1: {3 * v1}")
print(f"v1 == v2: {v1 == v2}")
v3 = Vector(2, 3)
print(f"v1 == v3: {v1 == v3}")
v1: (2, 3) v2: (1, 4) v1 + v2: (3, 7) v1 * 3: (6, 9) 3 * v1: (6, 9) v1 == v2: False v1 == v3: True