Skip to content
wannacode

Inheritance

Problem

Two copies of the same code

The payroll program has an Employee class with a name, a salary and a method for a raise. Then you needed a manager too, who also gets a bonus. The fastest way was to copy Employee into a new class Manager and add the bonus.

A month later someone enters a raise of -10% instead of 10%. So you add a check to Employee. You forget it in Manager (the method is raise_salary, because raise is a reserved word in Python):

# in Employee
def raise_salary(self, percent):
if percent > 0:
self._salary += self._salary * percent // 100
# in Manager: a copy from before the check
def raise_salary(self, percent):
self._salary += self._salary * percent // 100
ivan = Employee("Ivan", 2000)
maria = Manager("Maria", 3000, 500)
ivan.raise_salary(-10)
maria.raise_salary(-10)
print(ivan)
print(maria)
Output
Ivan: €2000
Maria: €2700

Ivan's salary is protected, but Maria's dropped by €300. The two classes are 90% the same, yet every fix has to be made twice.

The idea

The shared part goes in the parent

A manager is an employee, just with something extra. Say it in code: class Manager(Employee):. In Manager you write only what is new, the bonus:

class Manager(Employee):
def __init__(self, name, salary, bonus):
super().__init__(name, salary)
self._bonus = bonus
@property
def bonus(self):
return self._bonus
maria = Manager("Maria", 3000, 500)
maria.raise_salary(-10)
maria.raise_salary(10)
print(maria)
print(f"Bonus: €{maria.bonus}")
Output
Maria: €3300
Bonus: €500

Manager now has everything Employee has: a name, a salary, raise_salary() with the check, __str__(). That is why the -10% raise did not go through. The check is written once and works for both classes.

This is called inheritance. Employee is the parent (base class), and Manager is the child. The child takes everything from the parent and adds its own.

To check whether inheritance makes sense, use the sentence "X is a Y". "A manager is an employee" is true. "An invoice is an employee" is not, so the invoice does not inherit from Employee, even if it has an amount and a name.

Try it

What does the intern get?

Intern has nothing of its own: the body is just pass.

What will the program print?

class Intern(Employee):
pass
ana = Intern("Ana", 1200)
ana.raise_salary(5)
print(ana)
Answer

The idea

The parent comes first

Manager has its own __init__, because it also has a bonus. But the name and the salary are set up by the __init__ of Employee. Who calls it?

Python calls only one __init__: the one of the object's class. You have to call the parent one yourself:

def __init__(self, name, salary, bonus):
super().__init__(name, salary)
self._bonus = bonus

super() is "the parent of this class", and super().__init__(name, salary) calls its __init__ with these arguments. It sets up _name and _salary, and then Manager sets up _bonus. Python allows this line anywhere in __init__, but write it first: that way the parent's part is ready before the child uses it.

If you forget it, Python says nothing until the field is needed. print(maria) stops with AttributeError: 'Manager' object has no attribute '_name': the field was simply never created.

There is one object. It holds both the parent's part and the child's part, and each part is set up by its own constructor.

def __init__(self, name, salary, bonus):
super().__init__(name, salary)
self._bonus = bonus
maria = Manager("Maria", 3000, 500)
print(f"{maria}, bonus €{maria.bonus}")
Step by step, in words
  1. Manager(...) creates one object. For now it is empty: each field appears when __init__ gives it a value.
  2. __init__ of Manager starts with the three arguments.
  3. The parent comes first: super().__init__(name, salary) calls __init__ of Employee. Python does not call it for you.
  4. __init__ of Employee sets up its part: _name and _salary.
  5. Only then does the body of Manager set up the bonus.
  6. There is one object, and each part is set up by its own constructor.
More precisely, if you are curious
  • __init__ is inherited like any other method. That is why Intern, with no __init__ of its own, takes the name and the salary of Employee. You write your own __init__ when the child has something new, and then it replaces the parent's.
  • In older code you will see super(Manager, self).__init__(...) or Employee.__init__(self, ...). They do the same thing. super() with nothing in the parentheses is how you write it today.

Try it

Which constructor runs first?

Here the child does not call the parent explicitly:

What will the program print?

class Account:
def __init__(self):
print("Account opened")
class SavingsAccount(Account):
def __init__(self):
print("Interest: 3%")
savings = SavingsAccount()
Answer

Try it

Find the bug

Maria should have a salary of €3000 and a bonus of €500. But the program prints:

Output
Maria: €500

Which line is the bug on?

Pick the line with the bug.

The idea

The underscore: for the class and its children

At the end of the year, the manager's bonus is added to the salary. The method is in Manager, and the salary is _salary in Employee. The salary property only reads it, and here you have to change it.

In Java and C# there is a third access level for this, protected: the class and its children can see it, and everyone else cannot. Python has neither private nor protected. The underscore is a convention for exactly this: "internal, for the class and its children". That is why Manager writes straight to self._salary, and Employee does not change:

class Employee:
def __init__(self, name, salary):
self._name = name
self._salary = salary
# ... the rest is as before
class Manager(Employee):
# ... _bonus and __init__
def merge_bonus(self):
self._salary += self._bonus
self._bonus = 0
maria = Manager("Maria", 3000, 500)
maria.merge_bonus()
print(maria)
print(f"Bonus: €{maria.bonus}")
Output
Maria: €3500
Bonus: €0

The convention is not protection: maria._salary = 0 in the program also goes through, with no error. The underscore tells people "do not touch this from outside", and the child is "family". So the child uses it only when it really has to change the field, not to get around the check in raise_salary().

More precisely, if you are curious
  • Two underscores, self.__salary, are the closest thing to private. Python quietly renames the field to _Employee__salary. In Manager, self.__salary is already _Manager__salary, and there is no such field: AttributeError: 'Manager' object has no attribute '_Manager__salary'. This protects against accidental name clashes, not against people: you can still reach maria._Employee__salary.

Try it

What is missing?

An intern is an employee. What goes in the blank?

class Intern():
    pass

The idea

Parents and a common ancestor

In Java, C# and JavaScript, a class has exactly one parent. Python also allows several: class WorkingStudent(Employee, Student): gets the methods of both. This is called multiple inheritance. Here we will use one parent each. We will need several in the lesson on interfaces.

The chain can be long. Intern inherits from Employee. And what does Employee inherit from, since it has no parent in parentheses? Every class without a parent inherits from object. This is the common parent of all classes. That is why even an empty class has methods:

class Ticket:
pass
a = Ticket()
b = Ticket()
print(a == a)
print(a == b)
Output
True
False

== calls the method __eq__, and it comes from object. For now it only tells you whether the two variables point to the same object.

__str__ comes from there too. If you do not write it, print(ticket) prints something like <__main__.Ticket object at 0x7f3a2c1d5e50>: the class name and an address that is different on every run. In the "Classes and objects" module you wrote your own __str__. Now you know that this replaces the version from object. There is more on this in the next lesson.

More precisely, if you are curious
  • In Python there is no class that cannot be inherited from. typing.final promises it, but only a tool like mypy checks it, not Python itself.
  • With several parents, Python looks for a method in order: first in the class, then in the parents from left to right, and last in object. You can see the order in WorkingStudent.__mro__.

Exercise

Child classes

Solve the tasks in your editor. The parent classes are from the "Classes and objects" module. If you do not have them at hand, write them again: they are short.

1. Family car. SimpleCar has a speed and a method accelerate() that raises it by 10. Write FamilyCar, which inherits from SimpleCar, starts with 4 seats and has a method add_seat(). Create a family car, speed it up and add a seat:

Output
Speed: 10 km/h
Seats: 5

2. Student. Person has a name, an age and introduce(). Write Student, which inherits from Person and adds an average grade grade (a decimal number). Use the parent constructor.

Output
I am Elena, 19 years old.
Grade: 5.5

3. Breakdance. DanceMove has a name, a duration in seconds and perform(). Write BreakDanceMove with a complexity from 1 to 10 and a method show_complexity().

Output
Performing moonwalk for 5 seconds
Performing windmill for 12 seconds
windmill: complexity 8 out of 10

4. Electric car. Car has a brand, fuel in liters and the kilometers driven. refuel(liters) adds fuel, and drive(distance) uses 1 liter per 10 km and adds the kilometers (if there is not enough fuel, it says so and does not go). Write ElectricCar:

  • with no fuel, and with a battery that starts at 100%;
  • with an efficiency: how many km it drives on 1% of battery (5 for the "Tesla");
  • charge() charges the battery to 100%;
  • drive_electric(distance) uses battery instead of fuel and also adds the kilometers.
The child also changes the kilometers. What should the field in Car be called? Hint: the card on the underscore.
Output
Skoda: 150 km, fuel 35 L
Tesla: 200 km, battery 60%
Not enough battery for 400 km
Tesla: 600 km, battery 20%

More tasks

  • Worker. Worker inherits from Person and adds a job title and a method work():
Output
I am Peter, 34 years old.
My job: accountant
  • Intern with a mentor. Add a field with the mentor's name to Intern and pass it in the constructor, together with the name and the salary for the parent. Now Intern does need its own __init__.

Test

Check yourself

1. class Manager(Employee). Which is true?
2. The field _name is in Employee. Can Manager write self._name = "Maria" in one of its methods?
3. What does super().__init__(name, salary) do in the __init__ of Manager?
4. How many parents can a class have in Python?
5. Manager has its own __init__ that does not call super().__init__(). What happens?

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