Difficulty: Intermediate
Polymorphism means 'many forms' and refers to the ability of different objects to respond to the same method call in their own way. In Python, polymorphism is deeply embedded in the language. When you call len() on a list, a string, or a dictionary, each type handles the call differently through its own __len__ implementation. The caller does not need to know the specific type; it only needs to know that the object supports the operation.
Duck typing is Python's primary mechanism for polymorphism. The phrase comes from the saying: 'If it walks like a duck and quacks like a duck, it is a duck.' Python does not check an object's type before calling a method. It simply tries to call the method, and if the object has it, the call succeeds. This means unrelated classes can be used interchangeably as long as they implement the same interface (i.e., the same method names with compatible signatures).
Method overriding is the most explicit form of polymorphism. A subclass provides its own implementation of a method defined in its parent class. When the method is called on an instance of the subclass, the overriding version runs. This is commonly used with a base class that defines a generic interface and subclasses that provide specialized behavior.
Operator overloading lets you define how built-in operators (+, -, *, ==, <, etc.) work with your custom objects. You implement this by defining special (dunder) methods like __add__ for +, __eq__ for ==, __lt__ for <, and __mul__ for *. This makes your objects behave naturally with Python's syntax. For example, defining __add__ lets you write obj1 + obj2 instead of obj1.add(obj2).
Polymorphic functions and iteration are everywhere in Python. Built-in functions like len(), str(), iter(), and sorted() all work through duck typing. For-loops call __iter__ and __next__. The print function calls __str__. By implementing the right dunder methods, your custom objects integrate seamlessly with Python's built-in syntax and standard library functions.
class Dog:
def speak(self):
return 'Woof!'
class Cat:
def speak(self):
return 'Meow!'
class Robot:
def speak(self):
return 'Beep boop!'
def make_them_speak(things):
for thing in things:
print(thing.speak())
creatures = [Dog(), Cat(), Robot()]
make_them_speak(creatures)
Dog, Cat, and Robot have no common base class, yet make_them_speak works with all three because each implements speak(). This is duck typing: Python does not check the type, only that the method exists.
class Shape:
def area(self):
return 0
def describe(self):
return f'{self.__class__.__name__}: area = {self.area()}'
class Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side 2
class Triangle(Shape):
def __init__(self, base, height):
self.base = base
self.height = height
def area(self):
return 0.5 * self.base * self.height
shapes = [Square(4), Triangle(6, 3)]
for s in shapes:
print(s.describe())
describe() is defined once in Shape and calls self.area(). Because each subclass overrides area(), the same describe() method produces different output depending on the actual type. This is runtime polymorphism.
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 __eq__(self, other):
return self.x == other.x and self.y == other.y
def __repr__(self):
return f'Vector({self.x}, {self.y})'
v1 = Vector(1, 2)
v2 = Vector(3, 4)
v3 = v1 + v2
print(v3)
print(v1 + v2 == Vector(4, 6))
print(v1 == v2)
__add__ is called when you use + between two Vector objects. __eq__ handles ==. __repr__ provides the string representation used by print. These dunder methods make Vector work naturally with Python operators.
class Playlist:
def __init__(self, name, songs):
self.name = name
self.songs = songs
def __len__(self):
return len(self.songs)
def __str__(self):
return f'{self.name} ({len(self)} songs)'
def __contains__(self, song):
return song in self.songs
p = Playlist('Road Trip', ['Song A', 'Song B', 'Song C'])
print(len(p))
print(str(p))
print('Song B' in p)
print('Song D' in p)
By implementing __len__, __str__, and __contains__, the Playlist class works seamlessly with len(), str()/print(), and the 'in' operator. This is polymorphism through Python's protocol system.
duck typing, method overriding, operator overloading, __add__, __len__, polymorphic functions