Skip to content
wannacode

Polymorphism

Problem

Pay with an if for each kind

The company has regular employees, managers with a bonus and part-time interns. Someone made one Employee class with a "role" field, and the pay is worked out like this:

def monthly_pay(e):
if e.role == "manager":
return e.salary + e.bonus
elif e.role == "intern":
return e.salary // 2
return e.salary

It works. But then a new kind of employee comes along: a contractor. You need a new elif here, and in the function for taxes, and in the function for the payslip, and everywhere anyone asked about the role. And every employee carries a bonus field, even though only managers get a bonus.

The code for the manager is scattered all over the program instead of being in the manager.

The idea

Each class decides for itself

With inheritance from the last lesson, Manager and Intern are separate classes. Let each one say what its monthly pay is. The parent has a general version, and the child writes its own version of the same method:

class Employee:
# ... __init__, name and salary
def monthly_pay(self):
return self._salary
class Manager(Employee):
# ... _bonus and __init__
def monthly_pay(self):
return self.salary + self._bonus
ivan = Employee("Ivan", 2000)
maria = Manager("Maria", 3000, 500)
print(ivan.monthly_pay())
print(maria.monthly_pay())
Output
2000
3500

This is called overriding a method. The child writes a method with the same name. For objects of the child class, its version replaces the parent's.

You need nothing more: there is no keyword and no check. Any method can be overridden. Python looks only at the name, not at the parameters. That is handy, but it means nothing stops a mistake in the name or in the parameters. In a moment you will see what happens then.

More precisely, if you are curious
  • Since Python 3.12 there is @override from the typing module. You put it above the overriding method. Python itself does not check it: the program runs the same way with it and without it. A tool like mypy checks it, by reading the code before you run it.
  • There is also no way to forbid child classes from changing a method. @final from typing is only a note for such tools.

Try it

What changed?

The intern overrides only the method for the monthly pay:

What will the program print?

class Intern(Employee):
def monthly_pay(self):
return self.salary // 2
ana = Intern("Ana", 1600)
print(ana.monthly_pay())
print(ana.salary)
Answer

The idea

One list for all

A manager is an employee. So employees, managers and interns can sit in one list and be handled in one loop:

staff = [
Employee("Ivan", 2000),
Manager("Maria", 3000, 500),
Intern("Ana", 1600),
]
for e in staff:
print(f"{e.name}: €{e.monthly_pay()}")
Output
Ivan: €2000
Maria: €3500
Ana: €800

The loop calls the same method, e.monthly_pay(), and the result is different. The version that runs is the object's, not the one of the variable's type. This is called polymorphism: Greek for "many forms". One call, and each object answers in its own way.

Now a new kind of employee is a new class with its own monthly_pay(). The loop and the rest of the program stay as they are. There is no if on the role and no bonus field in employees who get no bonus.

The variable e has no type, so through it you can write anything you like, e.bonus too. If the object is a manager, it works. If it is a regular employee, the program stops on that line: AttributeError: 'Employee' object has no attribute 'bonus'. Python does not check ahead of time. How to ask objects what they can do is in the lesson "What is it really?".

See it

Which method runs

staff = [
Employee("Ivan", 2000),
Manager("Maria", 3000, 500),
Intern("Ana", 1600),
]
for e in staff:
print(f"{e.name}: €{e.monthly_pay()}")
Step by step, in words
  1. The list holds objects of three classes. Each slot holds an arrow.
  2. e points to Ivan, a plain Employee: Employee's monthly_pay runs.
  3. e points to Maria, a Manager: her own method runs, salary plus bonus.
  4. e points to Ana, an Intern: the intern gets half the salary.
  5. The same line, three different methods. The object e points to decides.

The list holds three arrows. Each one points to an object of a different class. On each round e points to the next object, and the call goes to that object's method: first in Employee, then in Manager, then in Intern.

The choice is made while the program runs. On each round Python looks for monthly_pay in the object's class first, then in its parent. Nobody checks ahead of time that every object has such a method. If one does not, the program stops on its round.

Try it

Find the bug

The program is the same, but the intern gets the full salary:

Output
Ivan: €2000
Maria: €3500
Ana: €1600

Here is the Intern class. Which line has the bug?

Pick the line with the bug.

The idea

Add to the parent, do not rewrite it

Sometimes the child does not want to replace the method completely, only to add something to it. A manager is described like an employee, plus the bonus. You call the parent's version with super().__str__():

def __str__(self):
return f"{super().__str__()}, bonus €{self._bonus}"
print(Employee("Ivan", 2000))
print(Manager("Maria", 3000, 500))
Output
Ivan (€2000)
Maria (€3000), bonus €500

If Employee changes how it describes itself, the manager follows on its own.

This is also the __str__ from the "Classes and objects" module. There you were already overriding a method: the version in object.

More precisely, if you are curious
  • You can call super().method() from any method of the child, not only from the overriding one.
  • Python does not compare the parameters. If you write def monthly_pay(self, extra): in Manager, it is still an override, and the loop that calls e.monthly_pay() stops at the manager: TypeError: Manager.monthly_pay() missing 1 required positional argument: 'extra'. So the overriding method takes the same parameters as the parent's.

Try it

What is missing?

The intern is described like an employee plus the word "intern". What goes in the blank?

class Intern(Employee):
    def __str__(self):
        return f"{.__str__()}, intern"

Exercise

Each in its own way

Solve the tasks in your editor. Each task has a list of objects of several classes and one loop.

1. Animal shelter. An Animal class with a name and a make_sound() method that prints "some sound". Dog and Cat override it. Put one of each of the three in a list:

Output
Guest: some sound
Rex: Woof!
Mitzi: Meow, meow!

2. The student introduces themselves. Student from the last lesson already has a grade. Override introduce() so that it says the grade too. Use super().introduce(); do not copy the line from Person.

Output
I am Georgi, 45 years old.
I am Elena, 19 years old.
Average grade: 5.5

3. Party. A Performance class with a title and a start() method. DanceMove, BreakDanceMove (inherits from DanceMove, adds difficulty) and Song override it. A Party class holds a list of performances and has a start_party() method that starts everything in order and at the end says the party has started.

Output
Dancing moonwalk
Dancing windmill
difficulty 8 out of 10
Singing 'Bohemian Rhapsody'
Starting raffle
The party has started!

More tasks

  • Contractor. Add a Contractor to the employees, with hours and a rate per hour. Their monthly pay is hours × rate. Work out the payroll for Ivan, Maria, Ana and Peter (60 hours at €25):
Output
Payroll: €7800
  • Payslip. Give Employee a payslip() method that returns the text "name: €pay" with the monthly pay. Write it only in Employee. Why does it work correctly for the manager and for the intern too?

Test

Check yourself

1. e = Manager("Maria", 3000, 500). Which monthly_pay runs on e.monthly_pay()?
2. staff = [Employee("Ivan", 2000), Manager("Maria", 3000, 500)]. What happens on print(staff[0].bonus)?
3. What do you have to write before a method that overrides a parent's method?
4. What does super().__str__() do inside the child's __str__?
5. Manager has def monthly_pay(self, extra):, while Employee has no parameter. The loop calls e.monthly_pay() for every employee. What happens?

Made by Ivo Gergov as part of Evo Tech's campaign for accessible programming education ·