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 barkThe 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 winsPython 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 tailsuper().__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:
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
__init__ but you passed arguments; the parent's __init__ expects only self+name. Add a child __init__ with super().__init__().__init__ replaces the parent's entirely; parent attributes never get set. Always call super first.✅ Checkpoint
class Dog(Animal) mean? *(Dog inherits from Animal — gets all its methods)*super().__init__(name) do? *(Runs the parent's setup, so the child doesn't repeat it)*Next: dunder methods — making your objects work with print, len, and comparison.