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,CatandBirdshare a common parent. - Constructors: the parent's
__init__stores the animal'sname, 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 callssound(). - 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
- Write the three subclasses. Each one lists
Animalin parentheses and definessound(self)returning its text:"Woof","Meow"or"Tweet". The method returns the text instead of printing it, because the parent'sspeakdoes the printing and concatenates the result with the name. - Don't define
__init__. A subclass without its own__init__uses the parent's, soDog("Rex")already setsself.nameto"Rex". The lesson's intro says each subclass forwards a name to the parent; here Python does that automatically. - Build the list
animals = [Dog("Rex"), Cat("Whiskers"), Bird("Tweety")]. The order of the list is the order of the output. - Loop over the list with the variable
aand calla.speak(). The loop doesn't test the type of each animal. It only relies on the contract: everyAnimalhas aspeak.
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. Ifsounddoesprint("Moo")and returns nothing, it returnsNone. The text appears, and thenspeakfails withTypeError: can only concatenate str (not "NoneType") to str. - Forgetting
soundin a subclass. A class that doesn't override it stays abstract, and creating it raisesTypeErrornaming the missing method. - Overriding
__init__without calling the parent. If a subclass defines its own__init__and skipssuper().__init__(name), thenself.nameis never set andspeakraisesAttributeError. 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 ina.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.