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:
- An extra attribute
fire_power super().__init__()to setnameandhp- An overridden
speak()method - A
flamethrower(target)method that dealsfire_powerdamage - Create one
ElectricPokemonand oneFirePokemon, 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:
- Inherit from a base class
- Call
super().__init__() - Add layers in
__init__ - Override
forward()to define how data flows through the network
Exit Ticket
- What happens if you forget
super().__init__()inElectricPokemon? - In the PyTorch example above, what plays the role of
Animal? What plays the role ofElectricPokemon? - If a child class does not define its own
__init__, do you needsuper().__init__()? Why or why not?