Polymorphism in Python
What is Polymorphism?
Polymorphism means "many forms." In Python, it allows different classes to be treated through the same interface — the same method name works differently depending on the object calling it.
Method Polymorphism
class Dog:
def speak(self) -> str:
return "Woof!"
class Cat:
def speak(self) -> str:
return "Meow!"
class Duck:
def speak(self) -> str:
return "Quack!"
# Polymorphism — same interface, different behavior
animals = [Dog(), Cat(), Duck()]
for animal in animals:
print(animal.speak()) # Woof! / Meow! / Quack!
# Works with a function too
def make_sound(animal):
print(animal.speak()) # doesn't care about the type
make_sound(Dog()) # Woof!
make_sound(Cat()) # Meow!
Duck Typing
Python uses "duck typing" — if an object has the right methods, it works, regardless of its class. "If it walks like a duck and quacks like a duck, it's a duck."
class TextFile:
def read(self) -> str:
return "Reading from text file"
class NetworkStream:
def read(self) -> str:
return "Reading from network"
class DatabaseCursor:
def read(self) -> str:
return "Reading from database"
# This function works with ANY object that has a read() method
def process(source):
data = source.read()
print(f"Got: {data}")
process(TextFile()) # Got: Reading from text file
process(NetworkStream()) # Got: Reading from network
process(DatabaseCursor()) # Got: Reading from database
# Built-in polymorphism
print(len("hello")) # 5 — works on strings
print(len([1, 2, 3])) # 3 — works on lists
print(len({"a": 1})) # 1 — works on dicts
Operator Overloading
Python lets you define how operators like +, -, *, == work on your custom classes using dunder methods.
class Vector:
def __init__(self, x: float, y: float):
self.x = x
self.y = y
def __add__(self, other: "Vector") -> "Vector":
return Vector(self.x + other.x, self.y + other.y)
def __sub__(self, other: "Vector") -> "Vector":
return Vector(self.x - other.x, self.y - other.y)
def __mul__(self, scalar: float) -> "Vector":
return Vector(self.x * scalar, self.y * scalar)
def __eq__(self, other: "Vector") -> bool:
return self.x == other.x and self.y == other.y
def __abs__(self) -> float:
import math
return math.sqrt(self.x**2 + self.y**2)
def __str__(self) -> str:
return f"Vector({self.x}, {self.y})"
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # Vector(4, 6)
print(v2 - v1) # Vector(2, 2)
print(v1 * 3) # Vector(3, 6)
print(abs(v2)) # 5.0
print(v1 == Vector(1, 2)) # True
Abstract Base Classes
Use ABC and @abstractmethod to define an interface that subclasses must implement.
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self) -> float:
pass
@abstractmethod
def perimeter(self) -> float:
pass
def describe(self) -> str:
return f"Area: {self.area():.2f}, Perimeter: {self.perimeter():.2f}"
class Circle(Shape):
def __init__(self, radius: float):
self.radius = radius
def area(self) -> float:
import math
return math.pi * self.radius ** 2
def perimeter(self) -> float:
import math
return 2 * math.pi * self.radius
class Rectangle(Shape):
def __init__(self, w: float, h: float):
self.w = w
self.h = h
def area(self) -> float:
return self.w * self.h
def perimeter(self) -> float:
return 2 * (self.w + self.h)
shapes = [Circle(5), Rectangle(4, 6)]
for shape in shapes:
print(shape.describe())
# Shape() # TypeError — can't instantiate abstract class