Lesson 9 of 11
Polymorphism means "many forms": the same method call does the right thing for whatever
object it lands on. You've already built it — every class that overrides speak() is being
polymorphic.
class Dog:
def speak(self):
return "Woof!"
class Cat:
def speak(self):
return "Meow!"
class Cow:
def speak(self):
return "Moo!"
for animal in [Dog(), Cat(), Cow()]:
print(animal.speak())
The loop doesn't know or care which class it has. It just calls speak() and each object
answers in its own way.
Why it matters
Without polymorphism, that loop becomes a growing chain of type checks:
class Dog:
pass
class Cat:
pass
def speak(animal):
if isinstance(animal, Dog):
return "Woof!"
elif isinstance(animal, Cat):
return "Meow!"
return "..."
print(speak(Dog()))
Every new animal means editing that function. With polymorphism, a new animal is a new class — and the loop that prints them never changes. The behaviour lives with the thing it describes.
Duck typing
Notice the first example had no shared parent class. Python doesn't require one: if an object
has a speak() method, you can call it. This is duck typing — "if it walks like a duck and
quacks like a duck, treat it as a duck."
class Robot:
def speak(self):
return "Beep boop."
class Dog:
def speak(self):
return "Woof!"
for thing in [Dog(), Robot()]:
print(thing.speak())
A robot is not an animal, but it can still take part — it has the method the loop needs. Python cares about what an object can do, not what it is.
Inheritance is still useful when the classes genuinely share code. Duck typing means you don't need it just to get polymorphism.
Polymorphism with built-ins
The built-in functions work the same way. len() calls __len__, str() calls __str__, +
calls __add__ — the same call, different behaviour per type:
print(len("hello"))
print(len([1, 2, 3]))
print(len({"a": 1}))
print(1 + 2)
print("a" + "b")
print([1] + [2])
The next lesson shows how to plug your own classes into these.
A practical example
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 Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side ** 2
shapes = [Rectangle(3, 4), Square(5), Rectangle(2, 2)]
for shape in shapes:
print(shape.area())
print(sum([s.area() for s in shapes]))
sum() doesn't know what a Square is. It doesn't need to — each shape knows its own area.
Try it yourself
Give three shapes an area() method and total them up.
Checkpoint
- Polymorphism: one call, many implementations, chosen by the object.
- It replaces chains of
isinstancechecks with methods on each class. - Duck typing means a shared parent class isn't required.
- Built-ins like
len()and+are polymorphic through dunder methods.
Questions about this lesson
Log in to ask a question.