Python Polymorphism

In the last lesson you learned Inheritance. A child class gets all the abilities of a parent class. Now we take the next step. What if many different classes share the same method name? And what if each one should behave its own way? That is what polymorphism gives you.

🤔 Why Polymorphism?

Picture an app that holds a list of animals. A dog, a cat, a cow. You want each one to make its sound. Without polymorphism you end up writing code like this:

if animal_type == "dog":
print("Woof!")
elif animal_type == "cat":
print("Meow!")
elif animal_type == "cow":
print("Moo!")

See the problem? Every time you add a new animal, you go back and add another elif. The code keeps growing. It gets messy fast.

Polymorphism flips this around. Each class knows how to make its own sound. You just say “make your sound” and the right thing happens. No long if chain.

💡 What Polymorphism Means

The word looks scary but the idea is simple. “Poly” means many. “Morph” means form. So polymorphism means one name, many forms.

In Python it means this. The same method name can do different things. It depends on which class the object came from.

Think of a TV remote. The power button is always the “power button”. But press it on your TV and the TV turns on. Press the same kind of button on the AC and the AC turns on. One button name, a different result on each device. That is polymorphism.

🐾 The Same Method on Different Classes

Let’s build the animal example for real. We make three classes. Each one has a method with the exact same name. It is called speak(). But each speak() returns a different sound.

class Dog:
def speak(self):
return "Woof!"
class Cat:
def speak(self):
return "Meow!"
class Cow:
def speak(self):
return "Moo!"

Here is what is going on, point by point:

  • All three classes have a method called speak.
  • The name is the same, but the body is different in each class.
  • Dog.speak() gives "Woof!", Cat.speak() gives "Meow!", Cow.speak() gives "Moo!".

Now the nice part. We put one of each into a list and loop over them. We call speak() on every object the same way. Each object responds in its own voice.

animals = [Dog(), Cat(), Cow()]
for animal in animals:
print(animal.speak())

Running that prints:

Output

Woof!
Meow!
Moo!

Look at the loop again. It does not check the type of the animal. It never asks “are you a dog or a cat?”. It just says animal.speak() and trusts each object to do the right thing. That is the whole point.

Tip

Python does not care about the class. It only cares that the object has a speak() method it can call. This relaxed style is called duck typing. If it walks like a duck and quacks like a duck, Python treats it like a duck.

🔁 Method Overriding

Polymorphism often shows up together with inheritance. A child class can have a method with the same name as its parent. When that happens, the child’s version replaces the parent’s. We call this method overriding.

Let’s see it. We start with a parent class Animal that has a basic speak(). Then each child class overrides it with its own sound.

class Animal:
def speak(self):
return "Some generic animal sound"
class Dog(Animal):
def speak(self):
return "Woof!"
class Cat(Animal):
def speak(self):
return "Meow!"

Here is what each piece does:

  • Animal is the parent. Its speak() returns a plain default sound.
  • Dog inherits from Animal but writes its own speak(). The child version wins.
  • Cat does the same with its own sound.

Now let’s call speak() on one object from each class. We include a plain Animal too.

animals = [Animal(), Dog(), Cat()]
for animal in animals:
print(animal.speak())

This prints:

Output

Some generic animal sound
Woof!
Meow!

The Animal object falls back to the parent’s speak(), because it has no override. The Dog and Cat objects use their own overridden speak(). Same method name, a different result each time. Polymorphism and overriding working together.

Caution

Always run your code and read the real output. Do not assume what it prints.

🤝 Reusing the Parent with super()

Sometimes you do not want to throw away the parent’s method. You want to add to it. You can call the parent’s version inside the child using super(). Then you add your own bit.

This Cat keeps the parent’s message and adds its own sound after it.

class Animal:
def speak(self):
return "I am an animal."
class Cat(Animal):
def speak(self):
parent_words = super().speak()
return f"{parent_words} I say Meow!"
print(Cat().speak())

That prints:

Output

I am an animal. I say Meow!

So super().speak() runs the parent’s method and hands back its result. Then the child glues its own words on the end. You override, but you still reuse what the parent already said.

📋 One Method Name, Many Behaviours

Here is the same idea in a small table, so you can see it at a glance.

Object Method called Result
Dog speak() Woof!
Cat speak() Meow!
Cow speak() Moo!

Same call, three different results. The caller does not need to know or care which class it is talking to.

⚠️ Common Mistakes

A few things trip people up when they first use polymorphism:

  • Spelling the method name differently in each class. If Dog has speak() but Cat has say(), the loop breaks. The names must match exactly.
# ❌ Avoid: different method names break the shared loop
class Dog:
def speak(self):
return "Woof!"
class Cat:
def say(self): # wrong name, the loop calling speak() will crash on this
return "Meow!"
# ✅ Good: every class uses the same method name
class Dog:
def speak(self):
return "Woof!"
class Cat:
def speak(self):
return "Meow!"
  • Going back to type checks. If you find yourself writing if isinstance(animal, Dog) inside the loop, you have thrown polymorphism away. Let each class handle its own behaviour.
  • Forgetting self. Every method needs self as its first parameter. If you forget it, Python will complain when you call the method.

✅ Best Practices

Keep these habits and polymorphism stays clean and easy:

  • Give the shared action one clear name and use it everywhere (speak, area, draw, save).
  • Let the loop call that one method and trust the objects. Do not test their types.
  • Use super() when the child should extend the parent, not fully replace it.
  • Run the code and read the real output before you believe what it prints.

🧩 What You’ve Learned

A quick recap of the ideas from this lesson:

  • ✅ Polymorphism means one method name can behave differently on different classes.
  • ✅ You can put mixed objects in a list and call the same method on each, and each responds its own way.
  • ✅ Method overriding is when a child class replaces a parent method with its own version.
  • super() lets a child reuse the parent’s method and add to it instead of replacing it.
  • ✅ Python uses duck typing. It cares that the method exists, not which class the object is.

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    What does polymorphism let you do?

    Why: Polymorphism means one method name, many forms, behaving differently per class.

  2. 2

    In the loop `for animal in animals: print(animal.speak())`, how does Python know which sound to make?

    Why: The call is the same, but each object runs its own version of speak().

  3. 3

    What is method overriding?

    Why: Overriding is when the child writes its own version of a method the parent already had.

  4. 4

    What does super().speak() do inside a child class?

    Why: super() calls the parent's version so the child can reuse and extend it.

🚀 What’s Next?

You now know how one method name can wear many faces. Next we look at how to protect the data inside a class so it cannot be changed by accident.

Encapsulation

Share & Connect