What polymorphism is
Polymorphism, Greek for "many forms", means that one line of code can do different things depending on the object it is given. You write animal.speak() once, and the version of speak that runs is the one belonging to that particular animal. It is what turns inheritance and overriding into something practical: once Dog and Cat both override speak(), code that deals with animals no longer needs to know which kind it holds.
An example
class Animal:
def speak(self):
print("...")
class Dog(Animal):
def speak(self):
print("Woof")
class Cat(Animal):
def speak(self):
print("Meow")
animals = [Dog(), Cat()]
for a in animals:
a.speak()
Woof
Meow
The loop contains one call, a.speak(), yet the output differs on each pass. Python looks up speak on the actual object at the moment of the call, so the dog's override runs for the dog and the cat's for the cat.
Why it helps
A loop like this works for kinds of animals that do not exist yet. If you add a Cow class tomorrow with its own speak, the same loop prints "Moo" for it with no edit. Code that works today and keeps working as the family of classes grows is the point of polymorphism. The alternative is a chain of if checks on the kind of animal, which must be extended every time a class is added.
Python's version: the type of the object decides
In languages with declared types, a variable has a static type (what it was declared as) and the object has a dynamic type (what it is at run time). Polymorphism means the call follows the dynamic type. Python variables have no declared type at all, so only the object's actual class matters.
One consequence is that Python does not need a shared parent for this to work. Any object that has a suitable method can be used:
class Robot:
def speak(self):
print("Beep")
for a in [Dog(), Robot()]:
a.speak()
Woof
Beep
Robot is not an Animal, but it has speak(), and that is all the loop needs. This style is often called duck typing: if the object behaves the way the code requires, it is accepted. Inheritance remains a clear way to document the relationship, but Python does not demand it. The failure case is also clear: an object without the method, such as one of a class that has no speak, raises an AttributeError when the loop reaches it.
Built-in functions use the same idea. len works on strings, lists and dictionaries, and each type provides its own way to report its size.
The lesson's exercise
The starter code defines Animal, Dog and Cat with the overrides already in place. The task is to use them polymorphically at module level, below the classes.
- Build the list. Write
animals = [Dog(), Cat()]. A list can hold objects of different classes, and both are animals, so later code can treat them alike. - Loop and call. Write
for a in animals:and, indented inside the loop,a.speak(). The first pass printsWoofand the secondMeow, with no check of the kind.
class Animal:
def speak(self):
print("...")
class Dog(Animal):
def speak(self):
print("Woof")
class Cat(Animal):
def speak(self):
print("Meow")
animals = [Dog(), Cat()]
for a in animals:
a.speak()
Woof
Meow
Common mistakes
- Forgetting the parentheses when building the list.
[Dog, Cat]holds the classes themselves, andDog.speak()then fails because no object suppliesself. - Calling
speakoutside the loop. The call must be indented inside thefor, or it runs once, after the loop, on the last animal only. - Checking types by hand. Writing
if isinstance(a, Dog): ...for each kind defeats the purpose. Let the override choose. - A subclass that forgets to override. It inherits the parent's version, so a class that does not define
speakprints the parent's....
Key takeaways
- Polymorphism means one call, such as
a.speak(), runs different code depending on the object. - Python decides at the moment of the call, from the actual object's class.
- Methods are overridable by default in Python, so no special keyword is involved.
- Objects of unrelated classes work together when they provide the method that the code calls.