Episode 3: Building Brick Families (Inheritance)
Welcome Back, Architecture Extraordinaire! ๐ฐ
Remember Episodes 1 and 2 where we learned to build basic bricks and keep their secrets safe? Well, grab your family tree chart because today weโre talking about inheritance - or as I like to call it, โHow LEGO bricks get their genetics!โ
Think about it: A 2x4 LEGO brick shares a LOT with a 2x2 brick. Theyโre both made of the same ABS plastic, they both have those satisfying studs on top, they both hurt equally when you step on them at 3 AM. But theyโre also different - different sizes, different stud counts, different uses in your builds.
In Python, we donโt have to rewrite all that shared code. We can create a โparent brickโ and have โchild bricksโ inherit all the good stuff!
The LEGO Family Tree ๐ณ
Imagine LEGO headquarters has a master โBasicBrickโ blueprint. Every other brick - from tiny 1x1s to massive baseplates - starts with this blueprint and adds its own special features.
class BasicBrick:
"""The ancestor of all LEGO bricks - the Original Progenitor!"""
def __init__(self, color):
self.color = color
self.material = "ABS Plastic" # All LEGO bricks share this
self.manufacturer = "LEGO Group"
self.clutch_power = 10 # That satisfying click!
def describe(self):
"""Every brick can describe itself"""
return f"A {self.color} LEGO brick"
def connect(self, other_brick):
"""All bricks can connect!"""
print(f"*satisfying click* ๐")
return True
def __repr__(self):
return f"BasicBrick(color='{self.color}')"
# Create a basic brick
basic = BasicBrick("red")
print(basic.describe()) # "A red LEGO brick"
basic.connect(basic) # *satisfying click* ๐
Now, this BasicBrick is cool, but itโs pretty generic. Letโs create some specialized bricks that inherit from it!
Single Inheritance: The Classic 2x4 ๐งฑ
The classic 2x4 brick is basically a BasicBrick with extra features - like knowing its dimensions and calculating studs.
class StandardBrick(BasicBrick):
"""A standard rectangular brick - inherits from BasicBrick!"""
def __init__(self, color, length, width):
# Call the parent's __init__ - give credit where credit is due!
super().__init__(color)
# Add our own special attributes
self.length = length
self.width = width
self.studs = length * width
def describe(self):
"""Override the parent's describe method with more detail"""
# We can still use the parent's version if we want!
basic_description = super().describe()
return f"{basic_description} ({self.length}x{self.width}, {self.studs} studs)"
def calculate_volume(self):
"""New method that BasicBrick doesn't have!"""
# Standard LEGO brick height is ~9.6mm, but let's simplify to 1 unit
return self.length * self.width * 1
# Create a standard brick
red_brick = StandardBrick("red", 2, 4)
# Inherited methods work!
print(red_brick.color) # "red" (from BasicBrick)
print(red_brick.material) # "ABS Plastic" (from BasicBrick)
red_brick.connect(red_brick) # *satisfying click* ๐ (from BasicBrick)
# New methods work too!
print(red_brick.describe()) # "A red LEGO brick (2x4, 8 studs)"
print(red_brick.calculate_volume()) # 8
# It's BOTH a StandardBrick AND a BasicBrick!
print(isinstance(red_brick, StandardBrick)) # True
print(isinstance(red_brick, BasicBrick)) # True! ๐คฏ
Whatโs happening here?
-
StandardBrickinherits fromBasicBrick(see the parentheses?) -
super().__init__(color)calls the parentโs initialization - We add NEW attributes (
length,width,studs) - We OVERRIDE the
describe()method (rebellion! But polite rebellion) - We can access parent methods through
super()
The Magic of super(): Calling Your Parents ๐
The super() function is like calling your parents for advice. Youโre independent, but sometimes mom and dad know best!
class TechnicBrick(BasicBrick):
"""LEGO Technic - with holes for axles!"""
def __init__(self, color, holes):
# "Hey Mom (BasicBrick), can you handle the color?"
super().__init__(color)
# "Cool, I'll handle the holes myself"
self.holes = holes
self.cross_axle_compatible = True
def describe(self):
# Get parent's description
parent_desc = super().describe()
# Add our own flair
return f"{parent_desc} with {self.holes} axle holes โ๏ธ"
def connect(self, other_brick):
# Call parent's connect
super().connect(other_brick)
# Add our own behavior
print("Also connected via axle! ๐ฉ")
technic = TechnicBrick("black", 5)
print(technic.describe()) # "A black LEGO brick with 5 axle holes โ๏ธ"
technic.connect(technic)
# *satisfying click* ๐
# Also connected via axle! ๐ฉ
Multiple Inheritance: The Minifigure Dilemma ๐ค
Now things get weird. What if you want to combine features from MULTIPLE parent classes? Enter multiple inheritance - which is like having divorced parents who both want input on your life choices.
class HasStuds:
"""Mixin class for things with studs"""
def __init__(self):
self.has_studs = True
def count_studs(self):
return getattr(self, 'studs', 0)
class IsHinged:
"""Mixin class for things that can hinge"""
def __init__(self):
self.can_hinge = True
self.hinge_angle = 0
def rotate_hinge(self, degrees):
self.hinge_angle = degrees
print(f"Rotated to {degrees}ยฐ ๐")
class WindowFrame(BasicBrick, HasStuds, IsHinged):
"""A window frame - has studs AND can hinge! ๐ช"""
def __init__(self, color, studs):
# Call ALL parent __init__ methods
BasicBrick.__init__(self, color)
HasStuds.__init__(self)
IsHinged.__init__(self)
self.studs = studs
self.is_transparent = False
def describe(self):
base = super().describe()
return f"{base} - Window Frame with {self.studs} studs (hinges {self.hinge_angle}ยฐ)"
# Create a window
window = WindowFrame("white", 4)
print(window.describe()) # "A white LEGO brick - Window Frame with 4 studs (hinges 0ยฐ)"
window.rotate_hinge(90) # Rotated to 90ยฐ ๐
print(window.count_studs()) # 4
print(window.connect(window)) # *satisfying click* ๐
WARNING: Multiple inheritance is powerful but can get messy FAST. Like building a LEGO castle while your little sibling is โhelping.โ
Method Resolution Order (MRO): The Family Reunion Seating Chart ๐ช
When you have multiple parents, Python needs to know who to ask first. This is called Method Resolution Order, or โwho gets to talk at the family reunion.โ
class GrandparentBrick:
def speak(self):
return "Back in my day, bricks were just rectangles!"
class ParentBrickA(GrandparentBrick):
def speak(self):
return "I'm Parent A - I add colors!"
class ParentBrickB(GrandparentBrick):
def speak(self):
return "I'm Parent B - I add studs!"
class ChildBrick(ParentBrickA, ParentBrickB):
"""Child inherits from BOTH parents"""
pass
# Who speaks when child.speak() is called?
child = ChildBrick()
print(child.speak()) # "I'm Parent A - I add colors!"
# Python's MRO (Method Resolution Order)
print(ChildBrick.__mro__)
# (<class 'ChildBrick'>, <class 'ParentBrickA'>, <class 'ParentBrickB'>,
# <class 'GrandparentBrick'>, <class 'object'>)
Python checks in THIS order:
- ChildBrick (the kid gets to speak first)
- ParentBrickA (left-to-right in the inheritance list)
- ParentBrickB
- GrandparentBrick
- object (the ultimate ancestor of everything in Python)
Itโs like a family dinner - the youngest speaks first (because theyโre cute), then parents from left to right, then grandparents, then the universe itself.
Real-World Example: The LEGO City Vehicle Family ๐
Letโs build a complete vehicle hierarchy like LEGO City sets!
class Vehicle(BasicBrick):
"""Base class for all LEGO vehicles"""
def __init__(self, color, wheels):
super().__init__(color)
self.wheels = wheels
self.speed = 0
def describe(self):
return f"{self.color} vehicle with {self.wheels} wheels"
def accelerate(self, amount):
self.speed += amount
print(f"๐๏ธ Vroom! Now going {self.speed} studs/second")
def brake(self):
self.speed = 0
print("๐ Screeech! Stopped.")
class Car(Vehicle):
"""A standard LEGO car"""
def __init__(self, color, doors=4):
super().__init__(color, wheels=4) # Cars have 4 wheels
self.doors = doors
def describe(self):
parent_desc = super().describe()
return f"{parent_desc}, {self.doors} doors ๐"
def honk(self):
print("BEEP BEEP! ๐ฏ")
class Truck(Vehicle):
"""A LEGO truck - bigger and slower"""
def __init__(self, color, cargo_capacity):
super().__init__(color, wheels=6) # Trucks have 6 wheels
self.cargo_capacity = cargo_capacity
self.cargo = []
def describe(self):
parent_desc = super().describe()
return f"{parent_desc}, carries {self.cargo_capacity} items ๐"
def load_cargo(self, item):
if len(self.cargo) < self.cargo_capacity:
self.cargo.append(item)
print(f"Loaded {item}! ๐ฆ")
else:
print("Cargo full! ๐ฆโ")
def accelerate(self, amount):
# Trucks are slower (method override)
super().accelerate(amount // 2) # Half the acceleration
print("(Trucks are heavy! ๐)")
class RaceCar(Car):
"""A LEGO race car - specialized car!"""
def __init__(self, color, team):
super().__init__(color, doors=2) # Race cars have 2 doors
self.team = team
self.turbo_mode = False
def describe(self):
parent_desc = super().describe()
return f"{parent_desc} - {self.team} Racing Team ๐"
def activate_turbo(self):
self.turbo_mode = True
print("๐จ TURBO ACTIVATED! ๐จ")
def accelerate(self, amount):
if self.turbo_mode:
super().accelerate(amount * 3) # Triple speed!
print("TURBOOOO! ๐ฅ")
self.turbo_mode = False # Turbo runs out
else:
super().accelerate(amount)
# Let's test our vehicle family!
city_car = Car("red")
print(city_car.describe()) # "red vehicle with 4 wheels, 4 doors ๐"
city_car.accelerate(10) # ๐๏ธ Vroom! Now going 10 studs/second
city_car.honk() # BEEP BEEP! ๐ฏ
delivery_truck = Truck("yellow", cargo_capacity=5)
print(delivery_truck.describe()) # "yellow vehicle with 6 wheels, carries 5 items ๐"
delivery_truck.load_cargo("pizza") # Loaded pizza! ๐ฆ
delivery_truck.accelerate(10) # ๐๏ธ Vroom! Now going 5 studs/second
# (Trucks are heavy! ๐)
race_car = RaceCar("blue", "Lightning Squad")
print(race_car.describe()) # "blue vehicle with 4 wheels, 2 doors ๐ - Lightning Squad Racing Team ๐"
race_car.activate_turbo() # ๐จ TURBO ACTIVATED! ๐จ
race_car.accelerate(10) # ๐๏ธ Vroom! Now going 30 studs/second
# TURBOOOO! ๐ฅ
# They're all related!
print(isinstance(race_car, RaceCar)) # True
print(isinstance(race_car, Car)) # True
print(isinstance(race_car, Vehicle)) # True
print(isinstance(race_car, BasicBrick)) # True! ๐คฏ
Composition vs Inheritance: The Great Debate ๐ฅ
Sometimes inheritance isnโt the answer. Sometimes you want to BUILD with other objects instead of inheriting from them. Itโs like the difference between saying โI AM a brickโ vs โI HAVE bricks.โ
class Wheel:
"""A LEGO wheel - composition component"""
def __init__(self, diameter):
self.diameter = diameter
def roll(self):
print(f"๐ Rolling {self.diameter}mm wheel...")
class CarWithComposition:
"""A car built using composition instead of inheritance"""
def __init__(self, color, wheel_diameter):
# HAS-A relationship instead of IS-A
self.color = color
self.wheels = [Wheel(wheel_diameter) for _ in range(4)]
self.speed = 0
def drive(self):
print(f"Driving {self.color} car...")
for wheel in self.wheels:
wheel.roll()
# Composition in action
composed_car = CarWithComposition("green", 30)
composed_car.drive()
# Driving green car...
# ๐ Rolling 30mm wheel...
# ๐ Rolling 30mm wheel...
# ๐ Rolling 30mm wheel...
# ๐ Rolling 30mm wheel...
When to use Inheritance:
- โIS-Aโ relationship (a RaceCar IS-A Car)
- Shared behavior and attributes
- Natural hierarchy exists
When to use Composition:
- โHAS-Aโ relationship (a Car HAS wheels)
- Mix and match components
- More flexibility to change
Itโs like LEGO itself! You donโt inherit from a baseplate - you BUILD ON it!
Abstract Base Classes: The Blueprint Blueprint ๐
Sometimes you want to create a parent class that says โALL my children MUST implement these methods!โ - like LEGO corporate mandating that every brick must have a connect() method.
from abc import ABC, abstractmethod
class AbstractVehicle(ABC):
"""Abstract vehicle - can't be instantiated directly!"""
def __init__(self, color):
self.color = color
@abstractmethod
def move(self):
"""All vehicles MUST implement this!"""
pass
@abstractmethod
def make_sound(self):
"""All vehicles MUST make a sound!"""
pass
class Boat(AbstractVehicle):
"""A LEGO boat"""
def move(self):
print("โต Sailing smoothly...")
def make_sound(self):
print("TOOT TOOT! ๐ฏ")
class Helicopter(AbstractVehicle):
"""A LEGO helicopter"""
def move(self):
print("๐ WHIRRRR - taking off!")
def make_sound(self):
print("THWOP THWOP THWOP! ๐")
# Can't create an abstract vehicle directly!
# vehicle = AbstractVehicle("red") # โ TypeError: Can't instantiate abstract class
# But concrete vehicles work!
boat = Boat("blue")
boat.move() # โต Sailing smoothly...
boat.make_sound() # TOOT TOOT! ๐ฏ
heli = Helicopter("red")
heli.move() # ๐ WHIRRRR - taking off!
heli.make_sound() # THWOP THWOP THWOP! ๐
The LEGO Inheritance Philosophy ๐ง
Just like LEGOโs design philosophy:
- Start Simple: Build a good base class (BasicBrick)
- Extend Thoughtfully: Add features through inheritance (StandardBrick, TechnicBrick)
- Specialize Wisely: Create specialized subclasses only when needed (RaceCar)
- Respect the Studs: Donโt break the parentโs interface (super() is your friend)
- Compose When Confused: If inheritance feels wrong, try composition instead!
Your Mission, Should You Choose to Accept It ๐ฏ
Build a LEGO Minifigure family hierarchy! Create:
-
Minifigurebase class with head, torso, legs -
Workerclass (construction worker with tool belt) -
Astronautclass (with helmet and jetpack) -
Pirateclass (with peg leg and parrot!) - Make them all inherit from
Minifigure - Add unique methods for each (worker can
build(), astronaut canfloat(), pirate cansay_arr())
Bonus points if you use super() properly and add a pose() method to the base class!
Next Time on โLike LEGO, Love Pythonโ ๐ฌ
In Episode 4, weโll tackle Polymorphism - or as I like to call it, โHow Different Bricks Can Do the Same Thing in Different Ways!โ Weโll learn about duck typing (if it clicks like a LEGO, itโs a LEGO), method overriding in depth, and how to make your code as flexible as a LEGO Technic joint.
Until then, keep your inheritance trees pruned, your super() calls respectful, and your MRO crystal clear!
Happy building! ๐๏ธโจ
P.S. - If youโre arguing with your team about inheritance vs composition, just remember: LEGO uses BOTH. You inherit from brick designs, but you compose buildings from bricks. Donโt overthink it! Build something awesome! ๐งฑ๐
๐ฏ Key Takeaways:
- Inheritance lets child classes reuse parent code (DRY principle!)
-
super()calls parent methods without repeating code - MRO determines which parent speaks first in multiple inheritance
- Abstract classes force children to implement certain methods
- Composition (HAS-A) is often better than inheritance (IS-A)
-
isinstance()checks the family tree - LEGO vehicles > real vehicles ๐๏ธ
Top comments (0)