Chapter 2: Inheritance

Implementing inheritance

face Josiah Wang

Let us implement some of the classes in our RPG example. We will create a Character class, and a Enemy subclass that inherits from Character. For simplicity, we will only implement a subset of the attributes and methods.

Inheritance example

First, we implement the Character class (with only a subset of attributes/methods).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class Character:
    def __init__(self, name, health=50, strength=30, defence=20):
        self.name = name
        self.health = health
        self.strength = strength
        self.defence = defence

    def attack(self, character):
        character.health = character.health - self.strength
        print(f"Bam! {self.name} attacked {character.name}.", 
             f"{character.name}'s health is now {character.health}")

    def defend(self, character):
        self.health = self.health - character.strength * 0.25
        print(f"{self.name} defended against {character.name}.",
             f"{self.name}'s health is now {self.health}")

    def __str__(self):
        return f"{self.name} is a Character (health: {self.health}, "\
               f"strength: {self.strength}, defence: {self.defence})" 

We then define the Enemy class, making it a subclass of Character (Line 1).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Enemy(Character):
    def __init__(self, name, health=50, strength=30, defence=20, evilness=50):
        super().__init__(name, health, strength, defence)
        self.evilness = 50

    def evil_laugh(self):
        print("Hehehehehe!")    

    def __str__(self):
        return f"{self.name} is an Enemy (health: {self.health},"\
               f"strength: {self.strength}, defence: {self.defence},"\
               f"evilness: {self.evilness})" 

In Line 3, super() refers to an instance of the superclass - in this case it is Character. Think of it as the ‘higher self’, if that makes sense! You can also use Character.__init__(self, name, health, ...), but it is extra work because you have to pass in self as the first parameter and worry about the name of the superclass. And if the name of the superclass changes one day, you will have to change this line too. So just use super() to spare yourself all the pain!

Line 3 initialises Enemy’s attributes like self.name, self.health, self.strength and self.defence using the superclass’ __init__() method. Otherwise you will have to repeat yourself. If you skip this line, then your Enemy won’t have a name, health etc.

In Line 4, you initialise self.evilness separately, since this is specific to only the Enemy subclass.

Here is a bit of code to test your two classes. Copy all the code above and below and run it. Observe the output, and understand what is happening!

1
2
3
4
5
6
7
boy = Character("Boy", 100, 20, 10)
evilman = Enemy("Voldemort", 30, 50, 40, 100)
print(boy)
print(evilman)
evilman.attack(boy)
boy.defend(evilman)
boy.evil_laugh()  # You should get an error here. Why? What does it say?

You might notice that Enemy has fewer lines of code than Character, despite being more powerful. We did not have to explicitly define the attributes name, health, strength and defence in Enemy. Enemy inherits all these from Character, thus a simple call to the constructor of the superclass is sufficient.

evilman was also able to attack the poor boy without having to explicitly define the attack() method.

boy cannot however express an evil_laugh() because it neither implemented that method nor inherited that method from any superclass.