Inheriting from a base class
Some classes turn out to be almost the same. Say you're writing a small adventure game with wizards and knights. Both have a name and health points, both can take damage, and both print the same way. Only one thing differs: a wizard casts spells and a knight raises a shield. Written as two separate classes, much of the code is there twice:
class Wizard:
def __init__(self, name, health):
self.name = name
self.health = health
def take_damage(self, amount):
self.health -= amount
def cast_spell(self):
return f"{self.name} casts a fireball!"
class Knight:
def __init__(self, name, health):
self.name = name
self.health = health
def take_damage(self, amount):
self.health -= amount
def raise_shield(self):
return f"{self.name} raises a shield."
Code that is written twice has to be fixed twice, and sooner or later one copy gets fixed and the other doesn't. Merging the two into one class with every method isn't a good answer either: then every knight could cast spells.
A base class and derived classes
Inheritance solves this. You write what the classes share once, in a base class, and each special class inherits everything from it and adds only what is its own. To make a class inherit another, write the base class's name in parentheses after the new class's name:
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 cast_spell(self):
return f"{self.name} casts a fireball!"
class Knight(Character):
def raise_shield(self):
return f"{self.name} raises a shield."
elva = Wizard("Elva", 30)
bram = Knight("Bram", 50)
bram.take_damage(12)
print(elva)
print(bram)
print(elva.cast_spell())
print(bram.raise_shield())
Elva (30 health)
Bram (38 health)
Elva casts a fireball!
Bram raises a shield.Wizard and Knight are derived classes of Character. (You'll also hear subclass or child class for a derived class, and superclass or parent class for the base class.) Neither has a constructor of its own, so Wizard("Elva", 30) runs the constructor it inherits from Character. take_damage and __str__ are written once, but they work for both. And cast_spell can use self.name, because every wizard got its name from that same constructor.
Inheritance goes one way
A derived class gets everything its base class has, but not the other way around. A plain Character has no cast_spell:
class Character:
def __init__(self, name):
self.name = name
class Wizard(Character):
def cast_spell(self):
return f"{self.name} casts a fireball!"
elva = Wizard("Elva")
print(elva.cast_spell())
pip = Character("Pip")
print(pip.cast_spell())
Elva casts a fireball!Traceback (most recent call last):
File "main.py", line 14, in <module>
print(pip.cast_spell())
^^^^^^^^^^^^^^
AttributeError: 'Character' object has no attribute 'cast_spell'Two derived classes don't share their own methods either: a knight can't cast spells. A quick test of a design is the words "is a". A wizard is a character, so Wizard(Character) makes sense. A character isn't always a wizard, so the spells belong in Wizard only.