AI for Youth Academy Future Scholars Research Initiative

Year 1 · Week 16

Chapter 16: Method Overriding and super()

Last week you learned that a child class inherits everything from its parent. This week you will learn two more skills: how to replace (override) a parent method with a new version, and how to use super().__init__() to add extra attributes. These two patterns are used in almost every real Python library — including PyTorch, the most popular tool for building AI.

Session 7: Method Overriding and super()

Duration: 60 minutes

Learning Goals

  • Override a parent method in a child class
  • Write super().__init__() correctly in a child class
  • Recognize the pattern used in PyTorch and other AI libraries

Overriding — Replacing a Behavior (15 min)

Last week you learned that a child class can add new methods. But sometimes you don't want to add — you want to replace a parent method with a different version. This is called overriding.

Here's an example. All animals can make a sound, but each animal makes a different sound:

class Animal:
    def __init__(self, name, hp):
        self.name = name
        self.hp   = hp

    def speak(self):
        print(f'{self.name} makes a sound.')

    def eat(self):
        self.hp += 10
        print(f'{self.name} eats. HP: {self.hp}')


class Dog(Animal):
    def speak(self):              # OVERRIDES Animal.speak
        print(f'{self.name} says: Woof!')


class Cat(Animal):
    def speak(self):              # OVERRIDES Animal.speak
        print(f'{self.name} says: Meow!')

Now put them in a list and let each animal speak:

animals = [Dog('Rex', 80), Cat('Luna', 70), Dog('Buddy', 90)]
for a in animals:
    a.speak()

# Rex says: Woof!
# Luna says: Meow!
# Buddy says: Woof!

Try It

What happens if we create a plain Animal and call speak()?

mystery = Animal('???', 50)
mystery.speak()    # ??? makes a sound.  ← uses Animal's version

And what about a method we didn't override?

rex = Dog('Rex', 80)
rex.eat()        # Rex eats. HP: 90  ← Dog has no eat(), so it uses Animal's

super().__init__() — The Most Important Pattern (30 min)

The Problem

Last week you learned that a child class can have extra attributes. But there's a catch: who sets up the parent's attributes?

If the child class redefines __init__, Python does not automatically run the parent's __init__. You have to call it yourself. That's what super().__init__() is for.

Step by Step

Step 1: See what the parent does — it sets name and hp:

class Animal:
    def __init__(self, name, hp):
        self.name = name
        self.hp   = hp

    def eat(self):
        self.hp += 10
        print(f'{self.name} eats. HP: {self.hp}')

Step 2: The child needs an extra attribute: voltage. Use super().__init__() to let the parent handle name and hp first, then add voltage:

class ElectricPokemon(Animal):
    def __init__(self, name, hp, voltage):   # extra param: voltage
        super().__init__(name, hp)            # let Animal set name and hp
        self.voltage = voltage                # then add our extra attribute

    def thunder_shock(self, target):
        damage = self.voltage // 10
        target.hp -= damage
        print(f'{self.name} uses Thunder Shock for {damage} damage!')
        if target.hp <= 0:
            print(f'{target.name} fainted!')

    def speak(self):
        print(f'{self.name}: Pika Pika!')

Step 3: Test it! An ElectricPokemon object has both the parent's attributes and its own:

pikachu = ElectricPokemon('Pikachu', 100, 500)
print(pikachu.name)      # Pikachu   — from Animal (via super())
print(pikachu.hp)        # 100       — from Animal (via super())
print(pikachu.voltage)   # 500       — ElectricPokemon's own
pikachu.speak()          # Pikachu: Pika Pika!  ← overrides Animal.speak
pikachu.eat()            # Pikachu eats. HP: 110  ← inherited from Animal

The Complete Inheritance Pattern — Summary

Every time you write a child class with extra attributes, use this pattern:

class Child(Parent):
    def __init__(self, parent_params, own_params):
        super().__init__(parent_params)   # Step 1: let parent do its thing
        self.own_attr = own_params         # Step 2: add your own stuff

Pair Exercise

Create a FirePokemon(Animal) class with:

  1. An extra attribute fire_power
  2. super().__init__() to set name and hp
  3. An overridden speak() method
  4. A flamethrower(target) method that deals fire_power damage
  5. Create one ElectricPokemon and one FirePokemon, and have them battle
Show Example Answer
class FirePokemon(Animal):
    def __init__(self, name, hp, fire_power):
        super().__init__(name, hp)
        self.fire_power = fire_power

    def speak(self):
        print(f'{self.name}: Char char!')

    def flamethrower(self, target):
        target.hp -= self.fire_power
        print(f'{self.name} uses Flamethrower for {self.fire_power} damage!')
        if target.hp <= 0:
            print(f'{target.name} fainted!')

# Battle!
pikachu = ElectricPokemon('Pikachu', 100, 500)
charmander = FirePokemon('Charmander', 90, 18)

pikachu.thunder_shock(charmander)   # 500//10 = 50 damage
charmander.flamethrower(pikachu)     # 18 damage
print(pikachu)       # Pikachu (HP: 82)
print(charmander)    # Charmander (HP: 40)

The PyTorch Connection — You Can Read AI Code Now!

You might think super().__init__() looks advanced. But this exact pattern is what AI engineers write every single day. Here's how it's used in PyTorch — the most popular deep learning framework in the world:

# This is REAL PyTorch code — used to define a neural network
import torch.nn as nn

class MyNetwork(nn.Module):          # inherit from nn.Module (like inheriting from Animal)
    def __init__(self):
        super().__init__()                # call parent's init (like super().__init__(name, hp))
        self.layer1 = nn.Linear(10, 5)   # add own attributes (like self.voltage = voltage)
        self.layer2 = nn.Linear(5, 1)    # add another one

    def forward(self, x):               # override parent's forward (like overriding speak)
        x = self.layer1(x)
        x = self.layer2(x)
        return x

Every single PyTorch neural network uses this pattern:

  1. Inherit from a base class
  2. Call super().__init__()
  3. Add layers in __init__
  4. Override forward() to define how data flows through the network

Exit Ticket

  1. What happens if you forget super().__init__() in ElectricPokemon?
  2. In the PyTorch example above, what plays the role of Animal? What plays the role of ElectricPokemon?
  3. If a child class does not define its own __init__, do you need super().__init__()? Why or why not?

Key Vocabulary