Inheritance
Learn how classes can share and reuse code by inheriting from a parent class, so you write less and do more.
Imagine you are designing a zoo app. You have dogs, cats, and parrots. All of them have a name, make sounds, and need to eat. If you wrote a completely separate class for each animal, you would repeat the same code over and over.
Inheritance solves this. You write the shared stuff once in a parent class, then each animal gets its own class that automatically inherits everything from the parent. Less typing, fewer bugs, and cleaner code.
See it in action
— step through the idea, then dive into the details below.Stop Rewriting the Same Code
Inheritance lets one class borrow everything from another. Write shared code once in a parent class, and every child class gets it for free.
#The Parent Class (Superclass)
Start with a general Animal class. Every animal has a name and can eat. This class becomes the blueprint that other classes build on top of.
class Animal:
def __init__(self, name):
self.name = name
def eat(self):
print(f"{self.name} is eating.")
def speak(self):
print(f"{self.name} makes a sound.")#Creating a Child Class (Subclass)
To inherit from Animal, put the parent class name inside the parentheses when defining the child class: class Dog(Animal). This one line gives Dog all of Animal's attributes and methods for free.
The "is-a" relationship
Ask yourself: is a Dog an Animal? Yes! That's exactly when inheritance makes sense. A Dog is-a Animal, so it should inherit from Animal. You would not make Leash inherit from Animal because a leash is not an animal.
class Dog(Animal):
pass # Nothing extra yet — Dog inherits everything from Animal
fido = Dog("Fido")
fido.eat()
fido.speak()#Calling super().__init__() to Set Up the Parent
When a child class needs its own __init__ (for example, to store extra data), you must also run the parent's __init__. You do that with super().__init__(...). Think of super() as a polite way of saying: "Hey parent, please do your setup first."
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # runs Animal.__init__ to set self.name
self.breed = breed # then we add the Dog-specific attribute
fido = Dog("Fido", "Labrador")
print(fido.name)
print(fido.breed)
fido.eat()Forgetting super().__init__() breaks self.name
If you define __init__ in the child class but forget to call super().__init__(), the parent's setup code never runs. Attributes like self.name that the parent sets will be missing, and you will get an AttributeError.
Always call super().__init__(...) as the first line of the child's __init__.
#Overriding Methods
A Dog doesn't just "make a sound" — it barks. A Cat meows. You can override a parent method by defining a method with the same name in the child class. Python will use the child's version instead of the parent's.
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name)
self.breed = breed
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!")
fido = Dog("Fido", "Labrador")
whiskers = Cat("Whiskers")
fido.speak()
whiskers.speak()
whiskers.eat() # inherited from Animal — not overridden#Using super() to Extend a Method
Sometimes you want to add to what the parent does, not completely replace it. Call super().method_name() inside the child's override, then add your own code on top.
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name)
self.breed = breed
def eat(self):
super().eat() # runs Animal.eat first
print("*wags tail enthusiastically*")
fido = Dog("Fido", "Labrador")
fido.eat()#Seeing the Full Picture
Here is the complete example bringing everything together: one Animal parent and two child classes, each sharing common code while being unique in their own way.
class Animal:
def __init__(self, name):
self.name = name
def eat(self):
print(f"{self.name} is eating.")
def speak(self):
print(f"{self.name} makes a sound.")
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name)
self.breed = breed
def speak(self):
print(f"{self.name} says: Woof!")
class Cat(Animal):
def __init__(self, name, indoor):
super().__init__(name)
self.indoor = indoor
def speak(self):
print(f"{self.name} says: Meow!")
animals = [Dog("Fido", "Labrador"), Cat("Whiskers", True)]
for animal in animals:
animal.speak()
animal.eat()Check relationships with isinstance()
Python lets you verify the "is-a" relationship at runtime:
``python print(isinstance(fido, Dog)) # True print(isinstance(fido, Animal)) # True — Dog is-a Animal print(isinstance(fido, Cat)) # False ``
This is handy when you need to know what kind of object you are working with.
You define `class Bird(Animal)` and give it its own `__init__(self, name, can_fly)`. What should the first line inside that `__init__` be?
Key takeaways
- A child class inherits all attributes and methods from its parent class — write shared code once.
- Use `class Child(Parent):` to create a subclass; this expresses the "is-a" relationship.
- Always call `super().__init__(...)` inside the child's `__init__` so the parent can do its own setup.
- Override a parent method by defining one with the same name in the child — Python uses the child's version.
- Use `super().method()` inside an override when you want to extend, not completely replace, the parent's behaviour.
What does this code print?
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
print(f"{self.name} makes a sound.")
class Cat(Animal):
def speak(self):
print(f"{self.name} says: Meow!")
whiskers = Cat("Whiskers")
whiskers.speak()This code should print "Rex is eating." but it crashes instead. What is wrong?
class Animal:
def __init__(self, name):
self.name = name
def eat(self):
print(f"{self.name} is eating.")
class Dog(Animal):
def __init__(self, name, breed):
self.breed = breed
rex = Dog("Rex", "Poodle")
rex.eat()Complete the Dog class so it inherits from Animal and its __init__ correctly sets up both the parent and the breed attribute.
class Animal: def __init__(self, name): self.name = name class Dog(): def __init__(self, name, breed): .__init__(name) self.breed = breed fido = Dog("Fido", "Labrador") print(fido.name) print(fido.breed)
Put these lines in the right order to define a Bird class that inherits from Animal, stores a can_fly attribute, and overrides speak().
class Bird(Animal):
def __init__(self, name, can_fly):
super().__init__(name)
print(f"{self.name} says: Tweet!")self.can_fly = can_fly
def speak(self):
Create a Vehicle parent class with an __init__ that stores make and model, and a method describe() that prints something like "Toyota Camry". Then create two subclasses: - Car(Vehicle) — add a doors attribute and override describe() to also print the number of doors. - Motorcycle(Vehicle) — add a has_sidecar boolean and override describe() to mention whether it has a sidecar. Create one instance of each and call describe() on both.
Try it live — edit the code and hit Run to execute real Python: