Python Arena
Step 3 of 4

Calling the base class with super()

So far the derived classes had no constructor of their own: they used the one they inherited. Often, though, a derived class needs attributes of its own. A wizard needs mana, the energy that spells use up, so Wizard needs a constructor that takes the mana too. But a constructor in Wizard overrides the one in Character, like any other method, and then the base class's constructor doesn't run at all:

class Character:
    def __init__(self, name, health):
        self.name = name
        self.health = health

    def __str__(self):
        return f"{self.name} ({self.health} health)"


class Wizard(Character):
    def __init__(self, name, health, mana):
        self.mana = mana


elva = Wizard("Elva", 30, 100)
print(elva)
Python stops with
Traceback (most recent call last):
  File "main.py", line 16, in <module>
    print(elva)
    ~~~~~^^^^^^
  File "main.py", line 7, in __str__
    return f"{self.name} ({self.health} health)"
              ^^^^^^^^^
AttributeError: 'Wizard' object has no attribute 'name'

The wizard was created, but only self.mana was ever set: nothing gave it a name. Copying the two lines from Character's constructor would work, but that's the very repetition inheritance was meant to remove.

super()

super() gives you the base class's version of a method. A derived class's constructor calls the base class's constructor with super().__init__(...), and then sets only its own attributes:

class Wizard(Character):
    def __init__(self, name, health, mana):
        super().__init__(name, health)
        self.mana = mana

super().__init__(name, health) runs Character's constructor for this wizard, and that sets name and health. Leave self out of the parentheses: as with any method call, Python passes it for you.

Extending a method instead of replacing it

super() works with any method, not only the constructor. An overriding method can call the base class's version and add something to it. Below, the wizard's __str__ takes the text from Character's __str__ and adds the mana. A knight's armor stops the first few points of every hit, and the knight's take_damage lets the base class subtract what's left:

class Character:
    def __init__(self, name, health):
        self.name = name
        self.health = health

    def take_damage(self, amount):
        self.health -= amount

    def __str__(self):
        return f"{self.name} ({self.health} health)"


class Wizard(Character):
    def __init__(self, name, health, mana):
        super().__init__(name, health)
        self.mana = mana

    def __str__(self):
        return super().__str__() + f", {self.mana} mana"


class Knight(Character):
    def __init__(self, name, health, armor):
        super().__init__(name, health)
        self.armor = armor

    def take_damage(self, amount):
        if amount > self.armor:
            super().take_damage(amount - self.armor)


elva = Wizard("Elva", 30, 100)
bram = Knight("Bram", 50, 5)
bram.take_damage(12)
bram.take_damage(3)
print(elva)
print(bram)
Output
Elva (30 health), 100 mana
Bram (43 health)

The hit of 12 does 7 damage after the armor, and the hit of 3 does none. Notice the difference between super().__str__() and self.__str__(). Inside the wizard's __str__, self.__str__() would call the wizard's own __str__ again, which would call itself again, and so on until Python stops with an error. super() goes straight to the base class's version.

Your task

Challenge 1 of 3

Fill in the blank so that an episode's constructor lets the base class Video set the title and the length, and the program prints:

S2: The Long Night (52 min)
2

Challenge 2

A live recording is a song that was played at some venue. Write a class LiveSong that inherits Song. Its constructor takes a title, an artist and a venue, and lets Song's constructor set the title and the artist. Its __str__ returns what Song's __str__ returns, followed by (live at VENUE). Use super() for both. For example,

song = LiveSong("Blue Moon", "The Owls", "Cafe Aurora")
print(song)
print(song.venue)

should print:

Blue Moon by The Owls (live at Cafe Aurora)
Cafe Aurora

Challenge 3

A bank account lets its owner withdraw money when there's enough of it. Write a class FeeAccount that inherits BankAccount, for accounts where every withdrawal costs a fee:

  • The constructor takes the owner, the balance and the fee, and uses BankAccount's constructor for the owner and the balance.
  • withdraw(amount) takes the amount plus the fee from the balance. Like the original, it returns True when that worked, and when the balance isn't enough, it takes nothing and returns False. Let BankAccount's withdraw do the work.

For example,

account = FeeAccount("Mia", 100, 2)
print(account.withdraw(30))
print(account)
print(account.withdraw(68))
print(account)

should print:

True
Mia: 68 euros
False
Mia: 68 euros