Inheritance — Build on What Exists
Every app has things that are alike but not identical: a Car and a Bike are both Vehicles; a Dog and a Cat are both Animals; a TextPost and a VideoPost are both Posts. Inheritance lets one class reuse another’s code and then add its own twist.
The syntax: one parenthesis
class Animal: # the PARENT (base) class
def __init__(self, name):
self.name = name
def eat(self):
print(f"{self.name} is eating")
def sleep(self):
print(f"{self.name} is sleeping")
class Dog(Animal): # the CHILD — inherits from Animal
def bark(self):
print(f"{self.name} says Woof!")
class Cat(Animal):
def meow(self):
print(f"{self.name} says Meow")
class Dog(Animal) reads: “Dog is an Animal, plus more.” Dogs get eat() and sleep() for free, plus their own bark():
d = Dog("Rocky")
c = Cat("Whiskers")
d.eat() # Rocky is eating ← inherited from Animal
d.bark() # Rocky says Woof! ← Dog's own
c.eat() # Whiskers is eating ← inherited
c.bark() # AttributeError! — Cats don't bark
The parent’s code written once, used by every child. Add a play() method to Animal and instantly all pets can play.
Overriding — the child’s twist
Children can replace inherited methods with their own version:
class Bird(Animal):
def eat(self): # same name as parent's
print(f"{self.name} pecks at seeds")
b = Bird("Tweety")
b.eat() # Tweeety pecks at seeds — Bird's version wins
Python uses the child’s method if it exists; the parent’s is only a fallback. This is called overriding, and it’s how “all animals eat, but each in their own way” becomes code.
super() — extending without replacing
Often you don’t want to replace the parent’s behavior — you want to add to it. super() calls the parent’s version:
class Dog(Animal):
def __init__(self, name, breed): # Dog needs an EXTRA field
super().__init__(name) # let Animal handle name
self.breed = breed # then handle the extra
def eat(self):
super().eat() # do the normal eating...
print(f"{self.name} wags its tail") # ...then Dog's extra
d = Dog("Rocky", "Labrador")
print(d.breed) # Labrador
d.eat()
# Rocky is eating
# Rocky wags its tail
super().__init__(name) is the professional pattern you’ll see in every framework: let the parent set up its part, then set up yours. Without it, the child would have to re-implement all the parent’s setup.
The is-a test (when to inherit)
Inheritance models “is a” relationships:
- Dog is an Animal ✅
- Car is a Vehicle ✅
- Student has a Course ❌ — that’s composition (a Student object containing a Course object as an attribute), not inheritance
Quick test: say it out loud. “Cat is an Animal” sounds right. “Course is a Student” sounds absurd. If the sentence fails, don’t inherit — store the object as an attribute instead.
Common Errors & Fixes
TypeError: Dog() takes no arguments— the child has no__init__but you passed arguments; the parent’s__init__expects only self+name. Add a child__init__withsuper().__init__().- Forgot super().init() — child’s
__init__replaces the parent’s entirely; parent attributes never get set. Always call super first. AttributeErroron an inherited method — check the spelling, and confirm the class actually inherits from the right parent.
✅ Checkpoint
- What does
class Dog(Animal)mean? (Dog inherits from Animal — gets all its methods) - What’s overriding? (Child redefining a parent’s method with its own version)
- What does
super().__init__(name)do? (Runs the parent’s setup, so the child doesn’t repeat it) - Student “has a” Course — inheritance? (No — store the Course as an attribute instead)
Next: dunder methods — making your objects work with print, len, and comparison.