{"id":"inheritance-super","slug":"super","title":"Calling the base class with super()","text":"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:\n\n```python\nclass Character:\n    def __init__(self, name, health):\n        self.name = name\n        self.health = health\n\n    def __str__(self):\n        return f\"{self.name} ({self.health} health)\"\n\n\nclass Wizard(Character):\n    def __init__(self, name, health, mana):\n        self.mana = mana\n\n\nelva = Wizard(\"Elva\", 30, 100)\nprint(elva)\n```\n\n```crash\nTraceback (most recent call last):\n  File \"main.py\", line 16, in <module>\n    print(elva)\n    ~~~~~^^^^^^\n  File \"main.py\", line 7, in __str__\n    return f\"{self.name} ({self.health} health)\"\n              ^^^^^^^^^\nAttributeError: 'Wizard' object has no attribute 'name'\n```\n\nThe 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.\n\n### super()\n\n**`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:\n\n```python\nclass Wizard(Character):\n    def __init__(self, name, health, mana):\n        super().__init__(name, health)\n        self.mana = mana\n```\n\n`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.\n\n### Extending a method instead of replacing it\n\n`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:\n\n```python\nclass Character:\n    def __init__(self, name, health):\n        self.name = name\n        self.health = health\n\n    def take_damage(self, amount):\n        self.health -= amount\n\n    def __str__(self):\n        return f\"{self.name} ({self.health} health)\"\n\n\nclass Wizard(Character):\n    def __init__(self, name, health, mana):\n        super().__init__(name, health)\n        self.mana = mana\n\n    def __str__(self):\n        return super().__str__() + f\", {self.mana} mana\"\n\n\nclass Knight(Character):\n    def __init__(self, name, health, armor):\n        super().__init__(name, health)\n        self.armor = armor\n\n    def take_damage(self, amount):\n        if amount > self.armor:\n            super().take_damage(amount - self.armor)\n\n\nelva = Wizard(\"Elva\", 30, 100)\nbram = Knight(\"Bram\", 50, 5)\nbram.take_damage(12)\nbram.take_damage(3)\nprint(elva)\nprint(bram)\n```\n\n```output\nElva (30 health), 100 mana\nBram (43 health)\n```\n\nThe 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.\n","task":"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:\n\n```\nS2: The Long Night (52 min)\n2\n```\n","kind":"fill","seed":"class Video:\n    def __init__(self, title, minutes):\n        self.title = title\n        self.minutes = minutes\n\n    def __str__(self):\n        return f\"{self.title} ({self.minutes} min)\"\n\n\nclass Episode(Video):\n    def __init__(self, title, minutes, season):\n        [[super().__init__]](title, minutes)\n        self.season = season\n\n    def __str__(self):\n        return f\"S{self.season}: \" + super().__str__()\n\n\nif __name__ == \"__main__\":\n    episode = Episode(\"The Long Night\", 52, 2)\n    print(episode)\n    print(episode.season)\n","solution":"class Video:\n    def __init__(self, title, minutes):\n        self.title = title\n        self.minutes = minutes\n\n    def __str__(self):\n        return f\"{self.title} ({self.minutes} min)\"\n\n\nclass Episode(Video):\n    def __init__(self, title, minutes, season):\n        super().__init__(title, minutes)\n        self.season = season\n\n    def __str__(self):\n        return f\"S{self.season}: \" + super().__str__()\n\n\nif __name__ == \"__main__\":\n    episode = Episode(\"The Long Night\", 52, 2)\n    print(episode)\n    print(episode.season)\n","hints":["The base class's constructor is called through `super()`, and a constructor's name is `__init__`.","Type `super().__init__` into the blank. The arguments `(title, minutes)` are already there, without `self`."],"tests":[{"name":"Output","stdin":"","expect":"S2: The Long Night (52 min)\n2","hidden":false}],"require":[],"forbid":[],"more":[{"task":"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,\n\n```python\nsong = LiveSong(\"Blue Moon\", \"The Owls\", \"Cafe Aurora\")\nprint(song)\nprint(song.venue)\n```\n\nshould print:\n\n```\nBlue Moon by The Owls (live at Cafe Aurora)\nCafe Aurora\n```\n","kind":"code","seed":"class Song:\n    def __init__(self, title, artist):\n        self.title = title\n        self.artist = artist\n\n    def __str__(self):\n        return f\"{self.title} by {self.artist}\"\n\n\n# Write the class LiveSong here.\n\n\nif __name__ == \"__main__\":\n    song = LiveSong(\"Blue Moon\", \"The Owls\", \"Cafe Aurora\")\n    print(song)\n","solution":"class Song:\n    def __init__(self, title, artist):\n        self.title = title\n        self.artist = artist\n\n    def __str__(self):\n        return f\"{self.title} by {self.artist}\"\n\n\nclass LiveSong(Song):\n    def __init__(self, title, artist, venue):\n        super().__init__(title, artist)\n        self.venue = venue\n\n    def __str__(self):\n        return super().__str__() + f\" (live at {self.venue})\"\n\n\nif __name__ == \"__main__\":\n    song = LiveSong(\"Blue Moon\", \"The Owls\", \"Cafe Aurora\")\n    print(song)\n","hints":["The constructor is `def __init__(self, title, artist, venue):`. Its first line hands the title and the artist to the base class: `super().__init__(title, artist)`.","Then the constructor sets the one attribute of its own, `self.venue`.","In `__str__`, `super().__str__()` gives the text `Song` would print. Add ` (live at ...)` to it with `+`."],"tests":[{"name":"Test 1","stdin":"","call":"song = LiveSong(\"Blue Moon\", \"The Owls\", \"Cafe Aurora\")\nprint(song)\nprint(song.venue)\n","expect":"Blue Moon by The Owls (live at Cafe Aurora)\nCafe Aurora","hidden":false},{"name":"Test 2","stdin":"","call":"encore = LiveSong(\"Night Train\", \"Pia Rand\", \"the Old Mill\")\nstudio = Song(\"Night Train\", \"Pia Rand\")\nprint(encore)\nprint(encore.title)\nprint(studio)\n","expect":"Night Train by Pia Rand (live at the Old Mill)\nNight Train\nNight Train by Pia Rand","hidden":true}],"require":[{"pattern":"class\\s+LiveSong\\s*\\(\\s*Song\\s*\\)","message":"`LiveSong` should inherit `Song`: `class LiveSong(Song):`."},{"pattern":"super\\(\\)\\.__init__\\(","message":"Let `Song`'s constructor set the title and the artist with `super().__init__(...)`."},{"pattern":"super\\(\\)\\.__str__\\(","message":"Build the text on `Song`'s version: `super().__str__()`."}],"forbid":[]},{"task":"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:\n\n- The constructor takes the owner, the balance and the fee, and uses `BankAccount`'s constructor for the owner and the balance.\n- `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.\n\nFor example,\n\n```python\naccount = FeeAccount(\"Mia\", 100, 2)\nprint(account.withdraw(30))\nprint(account)\nprint(account.withdraw(68))\nprint(account)\n```\n\nshould print:\n\n```\nTrue\nMia: 68 euros\nFalse\nMia: 68 euros\n```\n","kind":"code","seed":"class BankAccount:\n    def __init__(self, owner, balance):\n        self.owner = owner\n        self.balance = balance\n\n    def withdraw(self, amount):\n        if amount > self.balance:\n            return False\n        self.balance -= amount\n        return True\n\n    def __str__(self):\n        return f\"{self.owner}: {self.balance} euros\"\n\n\n# Write the class FeeAccount here.\n\n\nif __name__ == \"__main__\":\n    account = FeeAccount(\"Mia\", 100, 2)\n    print(account.withdraw(30))\n    print(account)\n","solution":"class BankAccount:\n    def __init__(self, owner, balance):\n        self.owner = owner\n        self.balance = balance\n\n    def withdraw(self, amount):\n        if amount > self.balance:\n            return False\n        self.balance -= amount\n        return True\n\n    def __str__(self):\n        return f\"{self.owner}: {self.balance} euros\"\n\n\nclass FeeAccount(BankAccount):\n    def __init__(self, owner, balance, fee):\n        super().__init__(owner, balance)\n        self.fee = fee\n\n    def withdraw(self, amount):\n        return super().withdraw(amount + self.fee)\n\n\nif __name__ == \"__main__\":\n    account = FeeAccount(\"Mia\", 100, 2)\n    print(account.withdraw(30))\n    print(account)\n","hints":["The constructor calls `super().__init__(owner, balance)` and then stores the fee in `self.fee`.","`withdraw` can hand the whole job to the base class: withdrawing 30 with a fee of 2 is the same as a plain withdrawal of 32.","Return what the base class's version returns: `return super().withdraw(...)`, with the amount plus the fee in the parentheses."],"tests":[{"name":"Test 1","stdin":"","call":"account = FeeAccount(\"Mia\", 100, 2)\nprint(account.withdraw(30))\nprint(account)\nprint(account.withdraw(68))\nprint(account)\n","expect":"True\nMia: 68 euros\nFalse\nMia: 68 euros","hidden":false},{"name":"Test 2","stdin":"","call":"account = FeeAccount(\"Ole\", 50, 5)\nprint(account.withdraw(45))\nprint(account)\nprint(account.withdraw(0))\nprint(account.fee)\n","expect":"True\nOle: 0 euros\nFalse\n5","hidden":true},{"name":"Test 3","stdin":"","call":"plain = BankAccount(\"Ana\", 20)\nprint(plain.withdraw(20))\nprint(plain)\n","expect":"True\nAna: 0 euros","hidden":true}],"require":[{"pattern":"class\\s+FeeAccount\\s*\\(\\s*BankAccount\\s*\\)","message":"`FeeAccount` should inherit `BankAccount`."},{"pattern":"super\\(\\)\\.__init__\\(","message":"Let `BankAccount`'s constructor set the owner and the balance with `super().__init__(...)`."},{"pattern":"super\\(\\)\\.withdraw\\(","message":"Let `BankAccount`'s `withdraw` do the work: call it with `super().withdraw(...)`."}],"forbid":[]}]}