Inheritance — the basics
How one class can reuse and extend another's behavior — the mechanism the rest of this domain's lessons (composition, Liskov, the patterns) are all reacting to in one way or another.
3 min read
A subclass reuses everything from its parent, automatically
class Animal:
def __init__(self, name):
self.name = name
def eat(self):
print(f"{self.name} is eating")
class Dog(Animal): # Dog inherits from Animal
def bark(self):
print(f"{self.name} says woof")
rex = Dog("Rex")
rex.eat() # "Rex is eating" — inherited from Animal
rex.bark() # "Rex says woof" — defined directly on Dogclass Dog(Animal): makes Dog a subclass of Animal (the parent or base class) — every method and attribute Animal defines is automatically available on Dog too, without rewriting any of it. Dog didn't need its own __init__ or eat method; it got both for free just by inheriting from Animal, and only had to define what's actually new and specific to dogs (bark).
Overriding: replacing inherited behavior with something more specific
class Animal:
def make_sound(self):
print("...")
class Dog(Animal):
def make_sound(self): # overrides Animal's version
print("Woof!")
class Cat(Animal):
def make_sound(self): # overrides Animal's version, differently
print("Meow!")
Dog().make_sound() # "Woof!"
Cat().make_sound() # "Meow!"A subclass can override a method it inherits — define a method with the same name, and the subclass's version replaces the parent's for instances of that subclass. Dog and Cat each override make_sound with their own specific behavior, while still inheriting anything else Animal defines that they don't override.
super(): calling the parent's version instead of replacing it entirely
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # call Animal's __init__ to set self.name
self.breed = breed # then add what's specific to Dog
rex = Dog("Rex", "Labrador")
print(rex.name, rex.breed) # Rex LabradorOften a subclass wants to extend the parent's behavior rather than fully replace it — Dog still needs name set up exactly the way Animal does it, plus its own extra breed. super().__init__(name) explicitly calls the parent class's __init__, so that logic doesn't need to be duplicated; the subclass adds only what's genuinely new. Without super().__init__(name) here, Dog instances would never actually get a self.name attribute set at all.
Polymorphism: the practical payoff of inheritance
animals = [Dog("Rex"), Cat("Tom")]
for animal in animals:
animal.make_sound() # calls the right version for each one automaticallyWoof!
Meow!
Code that works with Animal objects in general — like this loop — doesn't need to know or check whether each one is actually a Dog or a Cat; calling .make_sound() automatically runs whichever version the actual object's class defines. This is polymorphism: the same code working correctly with any subtype, without needing type-specific branching (if isinstance(animal, Dog): ...). It's the practical reason inheritance is useful beyond just avoiding repeated code — it lets you write code once against a general type and have it correctly handle every specific kind of that type.
Where this gets more nuanced — worth knowing it exists
Inheritance looks simple in small examples like these, but real designs run into real questions: when should a shared behavior be pulled up into a parent class vs. kept separate, what happens when a subclass genuinely can't honor everything its parent promises (covered in the Liskov Substitution lesson), and when composition — building behavior out of separate, swappable pieces instead of a class hierarchy — is actually the better fit (covered in its own lesson, composition over inheritance). This lesson is the mechanical foundation; those lessons are about the judgment calls that come after you're comfortable with the mechanics.
Further reading
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. What does a subclass get automatically just by inheriting from a parent class?
2. What does it mean for a subclass to 'override' a method?
3. What problem does super().__init__(...) solve?
4. What does this loop demonstrate: `for animal in animals: animal.make_sound()`?