Inheritance

Advanced · 18 min read · ▶ live playground · ✦ checkpoint

Three reward classes can share the same owner, points, and summary code. You should not have to paste that code three times. Inheritance is the OOP tool that lets one class start from another class, reuse its behavior, and change only the parts that are different.

Make a subclass

A parent class is the class another class builds from. A subclass is a class that inherits from a parent class, which means it can use the parent's attributes and methods unless it defines its own version.

Keep the house-blueprint picture from the last section. Inheritance is like making a revised blueprint from an existing blueprint: the revised plan starts with the old rooms already drawn, then adds or changes a few details.

class Reward:
    def __init__(self, player, points):
        self.player = player
        self.points = points
 
    def summary(self):
        return f"{self.player} earned {self.points} points"
 
 
class QuestReward(Reward):
    pass
 
 
reward = QuestReward("Mina", 50)
 
print(reward.player)
print(reward.summary())

Mina
Mina earned 50 points

class QuestReward(Reward): says QuestReward inherits from Reward. The name in parentheses is the parent class.

QuestReward has no body of its own yet, but QuestReward("Mina", 50) still works. Python uses Reward.__init__ to set up the instance, then reward.summary() uses Reward.summary.

This is behavior reuse, which means using code from an existing class instead of copying it into a new class. It keeps the shared rule in one place. If reward summaries need a new sentence later, you change Reward.summary, not five pasted methods.

Override the part that changes

A subclass gets more useful when it changes one small part. Method overriding is defining a method in a subclass with the same name as a method in the parent class, so the subclass's version wins for subclass instances.

class Reward:
    def __init__(self, player, points):
        self.player = player
        self.points = points
 
    def label(self):
        return "reward"
 
    def summary(self):
        return f"{self.player} earned {self.points} points ({self.label()})"
 
 
class QuestReward(Reward):
    def label(self):
        return "quest reward"
 
 
class BonusReward(Reward):
    def label(self):
        return "bonus reward"
 
 
quest = QuestReward("Mina", 50)
bonus = BonusReward("Ravi", 20)
 
print(quest.summary())
print(bonus.summary())

Mina earned 50 points (quest reward)
Ravi earned 20 points (bonus reward)

Both subclasses reuse __init__ and summary. They override only label.

Notice the small handoff inside summary: self.label() uses the label method on the actual object. For quest, that is QuestReward.label. For bonus, that is BonusReward.label. The parent method stays general, and the subclass supplies the detail.

Use overriding when the shape of the behavior is the same, but one step has a subclass-specific answer.

Use super() for shared setup

Sometimes a subclass needs all the parent's setup plus one more attribute. You could copy the whole parent __init__, but then the setup rule lives in two places. That is how bugs breed.

super() is a function that lets a subclass call a method from its parent class. In a subclass __init__, the common move is super().__init__(...): ask the parent to do its setup, then store the subclass's extra state.

class Reward:
    def __init__(self, player, points):
        self.player = player
        self.points = points
 
    def label(self):
        return "reward"
 
    def summary(self):
        return f"{self.player} earned {self.points} points ({self.label()})"
 
 
class StreakReward(Reward):
    def __init__(self, player, points, days):
        super().__init__(player, points)
        self.days = days
 
    def label(self):
        return f"{self.days}-day streak"
 
 
streak = StreakReward("Noor", 80, 14)
 
print(streak.player)
print(streak.days)
print(streak.summary())

Noor
14
Noor earned 80 points (14-day streak)

Read super().__init__(player, points) as "run the parent setup for the shared reward data." You do not pass self there. Python handles the current instance, the same way it handles self when you call normal instance methods.

After the parent setup runs, the subclass stores self.days. Now the object has the shared reward attributes and the streak-specific attribute.

Try it - add one reward type

Run the code, then add a new subclass named PracticeReward. Give it a label method that returns "practice reward", make one instance, and print its summary.

python — playgroundlive
⌘/Ctrl + Enter to run

Extend or replace parent behavior

Overriding comes in two common flavors.

Extending parent behavior means the subclass calls the parent method, then adds something before or after it. Replacing parent behavior means the subclass overrides the method and does not call the parent version at all.

Use extending when the parent behavior is still correct but incomplete. Use replacing when the parent behavior is the wrong behavior for that subclass.

class JournalEntry:
    def __init__(self, day, text):
        self.day = day
        self.text = text
 
    def format(self):
        return f"{self.day}: {self.text}"
 
 
class MoodEntry(JournalEntry):
    def __init__(self, day, text, mood):
        super().__init__(day, text)
        self.mood = mood
 
    def format(self):
        base_text = super().format()
        return f"{base_text} (mood: {self.mood})"
 
 
class PrivateEntry(JournalEntry):
    def format(self):
        return "This journal entry is private."
 
 
regular = JournalEntry("Monday", "Solved the loop bug")
mood = MoodEntry("Tuesday", "Helped Ravi debug", "proud")
private = PrivateEntry("Friday", "A thought for later")
 
print(regular.format())
print(mood.format())
print(private.format())

Monday: Solved the loop bug
Tuesday: Helped Ravi debug (mood: proud)
This journal entry is private.

MoodEntry.format extends the parent method. It calls super().format() to get the regular journal text, then adds mood text.

PrivateEntry.format replaces the parent method. It does not want the original text to appear, so it returns a completely different sentence.

That choice is design, not syntax. Before you override, ask: "Do I still want the parent behavior?" If yes, call super() and extend it. If no, replace it clearly.

Keep inheritance narrow

Inheritance is best when the subclass really is a more specific version of the parent. A StreakReward is a kind of Reward. A MoodEntry is a kind of JournalEntry.

Do not use inheritance just because two classes share a few attribute names. A Player and a LunchCard might both have an owner, but a player is not a kind of lunch card. Shared names are not enough. The relationship should read naturally in a sentence: "A streak reward is a reward."

This lesson uses single inheritance, which means each subclass has one parent class. Python can inherit from more than one parent, but that adds new rules and trade-offs, so it waits until lesson 11.3.

Checkpoint

Answer all three to mark this lesson complete

Inheritance lets you build a family of related classes without pasting the same methods everywhere. Next, you make those related objects more flexible: different classes answering the same method call in their own way.

+50 XP on completion