Object-Oriented ProgrammingIntermediate8 min41 / 63

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

Visual walkthrough1 / 6
1

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 magic phrase: if a Dog **is-a** Animal, Dog should inherit from Animal.

#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.

The parent class — Animal — holds everything all animals share.
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.

Think of it like

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.

Dog inherits eat() and speak() without us writing them again.
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."

super().__init__ handles the parent's setup; we only add what's new.
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()
Common mistake

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.

Each subclass overrides speak() while still sharing eat() from Animal.
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.

super().eat() runs the parent's logic; then we tack on dog-specific behaviour.
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.

Looping over different types that share a common interface — this is the power of inheritance.
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()
Tip

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.

Quick check

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.
Practice challenges
Test yourself · earn XP
0/4
Predict the output#1

What does this code print?

predict-output
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()
Fix the bug#2

This code should print "Rex is eating." but it crashes instead. What is wrong?

fix-bug
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()
Fill in the blank#3

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)
Reorder the lines#4

Put these lines in the right order to define a Bird class that inherits from Animal, stores a can_fly attribute, and overrides speak().

1
class Bird(Animal):
2
    def __init__(self, name, can_fly):
3
        super().__init__(name)
4
        print(f"{self.name} says: Tweet!")
5
        self.can_fly = can_fly
6
    def speak(self):
Your turn
Practice exercise

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:

solution.py · editable