38 Class Inheritance: Creating Character Specializations
In our previous lessons, we learned how to create classes and add methods to them. Today, we’ll explore inheritance - a powerful feature that lets us create new classes based on existing ones. Just as a Paladin is a special type of Warrior who also has holy powers, we can create specialized classes that build upon more basic ones.
38.1 What is Inheritance?
Inheritance allows us to create a new class that’s a special version of an existing class. The new class (called the child or subclass) gets all the attributes and methods of the original class (called the parent or superclass), and we can add new ones or modify existing ones.
Let’s start with a basic Character class and create specialized versions of it:
class Character:
def __init__(self, name, health, strength):
self.name = name
self.health = health
self.strength = strength
def attack(self, target):
print(f"{self.name} attacks {target} for {self.strength} damage!")
class Warrior(Character): # Warrior inherits from Character
def __init__(self, name, health, strength, weapon):
# First, set up the basic character attributes
super().__init__(name, health, strength)
# Then add warrior-specific attribute
self.weapon = weapon
def battle_cry(self):
print(f"{self.name} shouts: For glory!")
# Create a warrior
= Warrior("Parzival", 100, 15, "Excalibur")
hero
# Use both Character and Warrior methods
"Dragon") # From Character class
hero.attack(# From Warrior class hero.battle_cry()
This will output:
Parzival attacks Dragon for 15 damage!
Parzival shouts: For glory!
38.2 Creating Different Character Types
Let’s create several specialized character classes:
class Character:
def __init__(self, name, health, strength):
self.name = name
self.health = health
self.strength = strength
def attack(self, target):
print(f"{self.name} attacks {target} for {self.strength} damage!")
class Warrior(Character):
def __init__(self, name, health, strength, weapon):
super().__init__(name, health, strength)
self.weapon = weapon
def battle_cry(self):
print(f"{self.name} shouts: For glory!")
class Mage(Character):
def __init__(self, name, health, strength, mana):
super().__init__(name, health, strength)
self.mana = mana
def cast_spell(self, spell, target):
if self.mana >= 10:
print(f"{self.name} casts {spell} at {target}!")
self.mana -= 10
else:
print(f"{self.name} is out of mana!")
class Archer(Character):
def __init__(self, name, health, strength, arrows):
super().__init__(name, health, strength)
self.arrows = arrows
def shoot(self, target):
if self.arrows > 0:
print(f"{self.name} shoots an arrow at {target}!")
self.arrows -= 1
else:
print(f"{self.name} is out of arrows!")
# Create different character types
= Warrior("Parzival", 100, 15, "Excalibur")
warrior = Mage("Merlin", 80, 5, 100)
mage = Archer("Robin", 90, 10, 20)
archer
# Try out their abilities
"Dragon") # From Character class
warrior.attack(# From Warrior class
warrior.battle_cry() "Fireball", "Dragon") # From Mage class
mage.cast_spell("Dragon") # From Archer class archer.shoot(
38.3 Overriding Parent Methods
Sometimes we want a child class to do something differently than its parent class. We can override methods to do this:
class Character:
def __init__(self, name, health, strength):
self.name = name
self.health = health
self.strength = strength
def attack(self, target):
print(f"{self.name} attacks {target} for {self.strength} damage!")
class Warrior(Character):
def __init__(self, name, health, strength, weapon):
super().__init__(name, health, strength)
self.weapon = weapon
def attack(self, target): # Override the attack method
= 5
weapon_bonus = self.strength + weapon_bonus
total_damage print(f"{self.name} attacks {target} with {self.weapon}")
print(f"Dealing {total_damage} damage!")
# Compare the different attacks
= Character("Villager", 50, 5)
character = Warrior("Parzival", 100, 15, "Excalibur")
warrior
"Training Dummy")
character.attack("Training Dummy") warrior.attack(
This will output:
Villager attacks Training Dummy for 5 damage!
Parzival attacks Training Dummy with Excalibur
Dealing 20 damage!
38.4 Using super()
in Methods
The super()
function lets us call methods from the parent class. This is useful when we want to extend, rather than completely replace, a parent’s method:
class Character:
def __init__(self, name, health, strength):
self.name = name
self.health = health
self.strength = strength
def level_up(self):
self.health += 10
self.strength += 2
print(f"{self.name} reaches a new level!")
print(f"Health increased to {self.health}")
print(f"Strength increased to {self.strength}")
class Mage(Character):
def __init__(self, name, health, strength, mana):
super().__init__(name, health, strength)
self.mana = mana
def level_up(self):
# First, do the normal level up stuff
super().level_up()
# Then add mage-specific improvements
self.mana += 20
print(f"Mana increased to {self.mana}")
# Create and level up a mage
= Mage("Merlin", 80, 5, 100)
merlin merlin.level_up()
38.5 Practice Time: Class Inheritance
Now it’s your turn to work with inheritance! Try these challenges:
- Create a
Weapon
base class and several specialized weapon classes (Sword
,Bow
,Staff
) that inherit from it:
class Weapon:
def __init__(self, name, damage):
# Your code here
pass
def attack(self):
# Your code here
pass
class Sword(Weapon):
# Your code here
pass
class Bow(Weapon):
# Your code here
pass
- Make a
Spell
base class and create different types of spells that inherit from it:
class Spell:
def __init__(self, name, mana_cost):
# Your code here
pass
def cast(self, caster, target):
# Your code here
pass
class FireSpell(Spell):
# Your code here
pass
class IceSpell(Spell):
# Your code here
pass
- Create a
Monster
base class and several specific monster types:
class Monster:
def __init__(self, name, health, damage):
# Your code here
pass
class Dragon(Monster):
# Your code here
pass
class Troll(Monster):
# Your code here
pass
38.6 Common Bugs to Watch Out For
As you work with inheritance, be wary of these common pitfalls:
Forgetting
super().__init__()
: Always call the parent’s__init__
method in your child class constructors.class Wrong(Parent): def __init__(self, name): self.other = "Something" # Parent's __init__ never called! class Right(Parent): def __init__(self, name): super().__init__(name) # Call parent first self.other = "Something"
Method Resolution Order: Python looks for methods in the child class first, then the parent. Be aware of this when overriding methods.
Accessing Parent Methods: Use
super()
to access parent methods, don’t try to call them directly through the parent class name.Multiple Inheritance Complexity: While Python supports inheriting from multiple classes, it’s usually better to stick to single inheritance when learning.
Overriding Methods Incorrectly: When overriding methods, make sure the parameters match the parent class method.
38.7 Conclusion and Further Resources
You’ve now learned about inheritance, one of the most powerful features of object-oriented programming. With inheritance, you can create hierarchies of related classes, making your code more organized and reusable.
To learn more about Python inheritance, check out these resources:
- Python’s Official Tutorial on Inheritance
- Real Python’s Guide to Inheritance in Python
- W3Schools Python Inheritance
In our next lesson, we’ll explore some advanced class concepts including class methods, static methods, and properties. Keep practicing with inheritance, and soon you’ll be creating complex class hierarchies with ease!