Polymorphism means "many forms" — in Python, it's what lets you call the same method name on objects of different classes and have each one respond in its own way. You don't need to know exactly which subclass you're dealing with; you just call the method, and the right behavior happens automatically.

This builds directly on inheritance: a group of subclasses share a common method name from their parent, but each overrides it with its own implementation.

Polymorphism Through Method Overriding

Here, three different shape classes all implement an area() method, but each calculates it differently. Code that calls shape.area() doesn't need to know or care which shape it actually has.

class Shape:
    def area(self):
        return 0

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 round(3.14159 * self.radius ** 2, 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 = [Rectangle(4, 5), Circle(3), Triangle(6, 4)]

for shape in shapes:
    # same method call, different behavior per object
    print(f"{type(shape).__name__}: {shape.area()}")

Output:

Rectangle: 20
Circle: 28.27
Triangle: 12.0

Why This Matters

Without polymorphism, that loop would need an if/elif chain checking each shape's type and calling a differently-named method for each one — brittle code that has to change every time you add a new shape. With polymorphism, adding a new shape class that also defines area() just works with the existing loop, no changes required anywhere else.

Duck Typing: Python's Looser Form of Polymorphism

Python doesn't actually require these classes to share a common parent for polymorphism to work — if an object has the method you're calling, Python will call it, regardless of its class. This is often summarized as "if it walks like a duck and quacks like a duck, treat it like a duck." It's more flexible than polymorphism in strictly-typed languages, but it also means a typo in a method name only shows up as an error at runtime, not before.

See a complete worked class hierarchy demonstrating both inheritance and polymorphism together in the inheritance and polymorphism program example.