Start learning free

LESSON 182 OF 344 · OBJECT-ORIENTED PROGRAMMING

Inheritance in Python: Subclasses and Reuse

Lesson Read the full explanation

TL ; DR:

A child class can inherit from a parent class — it gets all the parent's fields and methods for free, and can add its own. This lets you reuse code instead of copy-pasting it.


class Puppy(Dog):
    def whine(self):
        print("whimper")


Full lesson:

Inheritance is one of the four classic OOP pillars (along with encapsulation, polymorphism, and abstraction). The idea is simple: most real-world things are kinds of other things — a Puppy is a Dog, a Car is a Vehicle, a Manager is an Employee. When you model that "is-a" relationship in code, the child class gets the parent's data and behavior automatically.

What gets inherited?

  • Fields — the Puppy has a name field even though we only declared it on Dog.
  • Methods — the Puppy can call bark() even though we only defined it on Dog.
  • The contract — anywhere code expects a Dog, you can pass a Puppy (we'll see this when we cover polymorphism).

What you still write

  • The constructor usually has to forward arguments up to the parent's constructor (because the parent's fields need initializing).
  • Any new methods or new fields specific to the child.

Your task

Below the Dog class, create a Puppy class that inherits from Dog and adds one new method, whine(), that prints "whimper". The harness creates a Puppy named "Buddy", calls its inherited bark(), and then its new whine().

Expected output:

Woof! I am Buddy
whimper
STEP 1 OF 2
    Ctrl + Enter to check

    PYTHON · OBJECT-ORIENTED PROGRAMMING

    Inheritance in Python, explained

    The idea

    Inheritance models an "is-a" relationship. A puppy is a dog, a car is a vehicle, a manager is an employee. When you write that relationship in code, the child class (also called a subclass) automatically receives the data and behavior of the parent class (the superclass). You write the shared parts once, and each child adds only what is different.

    Without it, a Puppy class would need its own copy of every Dog method, and a fix to one copy would have to be repeated in the other. With inheritance there is one copy, and the fix reaches every subclass at once.

    The syntax

    Name the parent in parentheses after the child's name:

    class Puppy(Dog):
        def whine(self):
            print("whimper")
    

    That is the whole mechanism. Puppy now has everything Dog has, plus whine.

    What is inherited

    • Methods. A Puppy can call bark() even though only Dog defines it.
    • Attributes. They are set by the parent's __init__, so a Puppy object has a name as soon as that constructor runs.
    • The type relationship. A Puppy object counts as a Dog, so code written for dogs also accepts puppies.
    print(isinstance(buddy, Dog))
    print(issubclass(Puppy, Dog))
    print(Puppy.__mro__)
    
    True
    True
    (<class '__main__.Puppy'>, <class '__main__.Dog'>, <class 'object'>)
    

    The last line is the method resolution order. When you call a method, Python looks in Puppy first, then Dog, then object, the base of every class, and uses the first match. That search order is how inheritance works at run time.

    The constructor

    A subclass that defines no __init__ of its own uses its parent's. So Puppy("Buddy") runs Dog.__init__ and sets the name. This is what the lesson relies on.

    If a subclass needs extra data, it writes its own __init__ and passes the shared part up with super():

    class Puppy(Dog):
        def __init__(self, name, toy):
            super().__init__(name)
            self.toy = toy
    

    Skipping the super().__init__(name) call is a classic bug. The parent's setup never runs, so self.name is never created, and the first call to bark() raises AttributeError: 'Puppy' object has no attribute 'name'.

    Chains of inheritance

    A subclass can itself be a parent. If a class inherits from Puppy, it receives whine from Puppy and bark and the constructor from Dog. Python walks up the chain until it finds the name, so an object of the bottom class is an instance of every class above it, and isinstance(x, Dog) is still True. Keep such chains short, because each level makes it harder to see where a method comes from.

    The lesson's exercise

    The starter code defines Dog with an __init__ that stores name and a bark method. At the bottom it creates buddy = Puppy("Buddy"), calls buddy.bark() and then buddy.whine(). You write the Puppy class between them.

    1. Declare the subclass. Write class Puppy(Dog): below Dog. Do not add an __init__: the one from Dog is exactly what Puppy("Buddy") needs.
    2. Add the new method. Inside Puppy, write def whine(self): and print "whimper". Only the new behavior needs code, because bark is inherited.
    class Dog:
        def __init__(self, name):
            self.name = name
        def bark(self):
            print("Woof! I am " + self.name)
    
    class Puppy(Dog):
        def whine(self):
            print("whimper")
    
    buddy = Puppy("Buddy")
    buddy.bark()
    buddy.whine()
    
    Woof! I am Buddy
    whimper
    

    Inheritance goes one way

    The child gets the parent's members, never the reverse. A plain Dog has no whine, and Dog("Rex").whine() raises AttributeError: 'Dog' object has no attribute 'whine'. Methods added in a subclass belong only to that subclass and its own children.

    Common mistakes

    • Writing the parent in the wrong place. class Puppy: Dog is a syntax error. The parent goes inside parentheses before the colon.
    • Defining Puppy before Dog. Python reads top to bottom, so the parent class must already exist when the subclass line runs; otherwise you get a NameError.
    • Using inheritance when the relationship is not "is-a". A dog has a name, but a dog is not a name. Use inheritance for kinds of things, and plain attributes for parts.

    Key takeaways

    • A subclass is written class Child(Parent): and inherits the parent's methods and attributes.
    • Without its own __init__, a subclass uses the parent's.
    • With its own __init__, call super().__init__(...) so the parent can set up its share.
    • Inheritance flows from parent to child only.
    Keep going with PythonAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free