Start learning free

LESSON 187 OF 344 · OBJECT-ORIENTED PROGRAMMING

OOP Capstone in Python: An Animal Hierarchy

Lesson Read the full explanation

TL ; DR:

Put it all together: an abstract parent class, three concrete subclasses, and a polymorphic loop that dispatches by runtime type. This is the shape of nearly every real OOP design.


from abc import ABC, abstractmethod
class Animal(ABC):
    def __init__(self, name):
        self.name = name
    @abstractmethod
    def sound(self): pass    # each subclass picks
    def speak(self):          # shared by all
        print(self.name + ": " + self.sound())
# Dog, Cat, Bird each override sound
# zoo = [Dog("Rex"), Cat("Whiskers"), Bird("Tweety")]
# for a in zoo: a.speak()


Full lesson:

This problem stitches together everything you've learned in this chapter:

  1. Inheritance — three concrete classes share a common parent.
  2. Constructors — each subclass forwards a name to the parent.
  3. Abstract methods — the parent declares sound() with no body; each child must provide one.
  4. Shared behavior — the parent has a concrete speak() that calls sound(); every subclass inherits it for free.
  5. Polymorphism — a collection typed as Animal holds a Dog, a Cat, and a Bird; speak() dispatches to the right sound() at runtime.

The shape of this code is the shape of nearly every real OOP design you'll see — a small contract at the top (the abstract parent), concrete variants below, and the rest of the program written against the abstract type. New variants get added without touching the consumer code.


Your task

The abstract Animal class is already defined, with a concrete speak() that calls the abstract sound(). Add three subclasses — Dog (returns "Woof"), Cat (returns "Meow"), and Bird (returns "Tweet"). Then in MainFunction / main, build a polymorphic collection holding one of each (Rex, Whiskers, Tweety in that order) and loop through it calling speak().

Expected output:

Rex: Woof
Whiskers: Meow
Tweety: Tweet
STEP 1 OF 2
    Ctrl + Enter to check

    PYTHON · OBJECT-ORIENTED PROGRAMMING

    Oop Capstone in Python, explained

    What the capstone brings together

    This lesson combines the main object-oriented ideas of the chapter in one small program. It has an abstract parent, Animal, three concrete subclasses and a loop that treats them all the same way.

    • Inheritance: Dog, Cat and Bird share a common parent.
    • Constructors: the parent's __init__ stores the animal's name, and the children reuse it.
    • Abstract methods: sound() is declared without a real body, and each child must provide one.
    • Shared behaviour: speak() is written once in the parent and calls sound().
    • Polymorphism: a list holds different kinds of animals, and each one answers speak() in its own way.

    Most larger designs follow this shape: a small contract at the top, concrete variants below it, and the rest of the program written against the contract.

    The parent class

    The starter code gives you Animal:

    from abc import ABC, abstractmethod
    
    class Animal(ABC):
        def __init__(self, name):
            self.name = name
        @abstractmethod
        def sound(self): pass
        def speak(self):
            print(self.name + ": " + self.sound())
    

    Two kinds of method live side by side. sound is abstract: it says "every animal has a sound" without saying which. speak is ordinary and complete. It builds a line from the animal's name and whatever sound() returns, then prints it. Because speak calls self.sound(), Python picks the version of sound that belongs to the actual object, which is what makes the design work.

    Animal inherits from ABC, so Animal("x") itself can't be created: it has an abstract method. A subclass must override sound before it can be instantiated.

    Step by step

    1. Write the three subclasses. Each one lists Animal in parentheses and defines sound(self) returning its text: "Woof", "Meow" or "Tweet". The method returns the text instead of printing it, because the parent's speak does the printing and concatenates the result with the name.
    2. Don't define __init__. A subclass without its own __init__ uses the parent's, so Dog("Rex") already sets self.name to "Rex". The lesson's intro says each subclass forwards a name to the parent; here Python does that automatically.
    3. Build the list animals = [Dog("Rex"), Cat("Whiskers"), Bird("Tweety")]. The order of the list is the order of the output.
    4. Loop over the list with the variable a and call a.speak(). The loop doesn't test the type of each animal. It only relies on the contract: every Animal has a speak.

    The solution in Python

    from abc import ABC, abstractmethod
    
    class Animal(ABC):
        def __init__(self, name):
            self.name = name
        @abstractmethod
        def sound(self): pass
        def speak(self):
            print(self.name + ": " + self.sound())
    
    class Dog(Animal):
        def sound(self): return "Woof"
    
    class Cat(Animal):
        def sound(self): return "Meow"
    
    class Bird(Animal):
        def sound(self): return "Tweet"
    
    animals = [Dog("Rex"), Cat("Whiskers"), Bird("Tweety")]
    for a in animals:
        a.speak()
    

    It prints:

    Rex: Woof
    Whiskers: Meow
    Tweety: Tweet
    

    Why the loop is polymorphic

    The same expression, a.speak(), runs once per animal. In each pass Python looks up speak on the object, finds the one inherited from Animal, and runs it. Inside, self.sound() is looked up on the same object, so a Dog gives "Woof" and a Cat gives "Meow". This is polymorphism: one call, different behaviour depending on the object's class.

    It also makes the program easy to extend. A Cow class with sound returning "Moo" can be added to the list and the loop needs no change.

    Common mistakes

    • Printing inside sound. If sound does print("Moo") and returns nothing, it returns None. The text appears, and then speak fails with TypeError: can only concatenate str (not "NoneType") to str.
    • Forgetting sound in a subclass. A class that doesn't override it stays abstract, and creating it raises TypeError naming the missing method.
    • Overriding __init__ without calling the parent. If a subclass defines its own __init__ and skips super().__init__(name), then self.name is never set and speak raises AttributeError. This lesson avoids the problem by not overriding it.
    • Getting the order wrong. The output follows the list, so Rex, Whiskers and Tweety must be in that order.
    • Calling the method on the class. Dog.speak() has no animal to work on. Call it on an instance, as in a.speak().
    • Mismatched text. The output must match exactly, including the colon and the single space after it.

    Key takeaways

    • An abstract parent defines the contract and the shared behaviour; subclasses fill in the differences.
    • A subclass that adds no __init__ inherits the parent's constructor.
    • A method defined once in the parent can call an abstract method, and Python runs the child's version at runtime.
    • Code written against the parent type, like the loop here, keeps working when new subclasses are added.
    Keep going with PythonAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free