All 4.2 28 min read 10 question quiz 23 exam marks

Inheritance and Polymorphism

Building new classes from existing ones, overriding behaviour, and why a single loop can correctly handle a dozen different kinds of object.

Inheritance

Inheritance lets a new class take on all the attributes and methods of an existing one, then add or change what it needs.

The existing class is the parent, superclass or base class. The new one is the child, subclass or derived class.

Python
class Animal:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def speak(self):
        return "Some sound"

    def describe(self):
        return f"{self.name} is {self.age} years old"


class Dog(Animal):
    def speak(self):
        return "Woof"


class Cat(Animal):
    def __init__(self, name, age, indoor=True):
        super().__init__(name, age)      # run the parent constructor
        self.indoor = indoor

    def speak(self):
        return "Meow"

    def describe(self):
        base = super().describe()        # reuse the parent version
        where = "indoor" if self.indoor else "outdoor"
        return f"{base} and is an {where} cat"


rex = Dog("Rex", 3)
tom = Cat("Tom", 5, indoor=False)

print(rex.describe())    # Rex is 3 years old
print(rex.speak())       # Woof
print(tom.describe())    # Tom is 5 years old and is an outdoor cat

What is happening

  • class Dog(Animal) means Dog inherits everything from Animal
  • Dog does not define describe, so it uses Animal's
  • Dog defines its own speak, which overrides the parent version
  • super() calls the parent's version of a method, which lets you extend rather than replace
Why inheritance is useful

Shared behaviour is written once in the parent. Every subclass gets it automatically, and a fix in the parent fixes it everywhere at once.

Dog and Cat inheriting from Animal, showing what is inherited, overridden and newA UML diagram of inheritance. The parent class Animal has the attributes name and age and the methods speak and describe. Two child classes, Dog and Cat, inherit from it, shown by lines ending in a hollow triangle pointing at Animal. Dog inherits name, age and describe unchanged, and overrides speak to return Woof. Cat inherits name and age, adds a new attribute, indoor, overrides speak to return Meow, and overrides describe while still calling the parent's version with super. A key says grey lines are inherited, coral lines are overridden and green lines are new in the subclass.Animalnameagespeak()describe()Dognameagedescribe()speak() -> 'Woof'Catnameageindoorspeak() -> 'Meow'describe() + super()inherits frominherited: used as it isoverridden: its own versionnew: only in this subclass1234
  1. The parent class holds what all animals share.
  2. Dog inherits everything and overrides speak.
  3. Cat adds an attribute of its own and overrides speak and describe.
  4. Inherited lines are used as they are, overridden lines replace the parent's version, and new lines exist only in the subclass.
Inheritance removes duplication: put shared behaviour in the parent, and only what differs in the children.

Polymorphism

Polymorphism means objects of different classes can be used through the same interface, each responding in its own way.

Python
animals = [Dog("Rex", 3), Cat("Tom", 5), Dog("Bella", 1)]

for animal in animals:
    print(f"{animal.name}: {animal.speak()}")

The loop never checks what kind of animal it has. It calls speak() and each object runs its own version. Adding a Horse class later requires no change to this loop at all.

Why this matters enormously

Without polymorphism the loop would need:

Python
for animal in animals:
    if isinstance(animal, Dog):
        print("Woof")
    elif isinstance(animal, Cat):
        print("Meow")
    # and another branch for every new animal, forever

Every new type of animal would mean editing every loop in the program. With polymorphism, new behaviour is added by writing a new class and nothing else changes.

An abstract base class

Sometimes the parent should never be instantiated on its own, only inherited from.

Python
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

    def describe(self):
        return f"{type(self).__name__} with area {self.area():.2f}"


class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14159 * self.radius ** 2


class Square(Shape):
    def __init__(self, side):
        self.side = side

    def area(self):
        return self.side ** 2


for shape in [Circle(3), Square(4)]:
    print(shape.describe())

Shape guarantees that every subclass provides an area method. Trying to create a Shape() directly raises an error, which is exactly right because a shape with no specific form has no area.

Polymorphism: one call to area works on a circle and a square, each using its own methodPolymorphism means one interface with many forms. The code loops over a list holding a Circle and a Square, and calls the same method on each one. The abstract class Shape guarantees that every subclass provides an area method. Calling shape dot area on the Circle with radius 3 uses the Circle's own version, 3.14159 times r times r, which gives 28.27. Calling it on the Square with side 4 uses the Square's own version, side times side, which gives 16.00. The loop does not need to know which kind of shape it has.shapes.pyclass Shape(ABC):    @abstractmethod    def area(self): ...for shape in [Circle(3), Square(4)]:    print(shape.describe())1One call, many forms2shape.area()Circle's ownarea()Square's ownarea()Circle(3)Square(4)r = 3side = 43.14159 x r x r= 28.27side x side= 16.0034
  1. The abstract class says every shape has an area method, and the loop calls it on each shape.
  2. One call, shape.area(), is made, and each object answers with its own version.
  3. A Circle works out its area with its own method.
  4. A Square uses its own method. The loop never checks the type, so new shapes can be added without changing it.
Polymorphism lets code work with many types through one interface, so new types need no changes to existing code.

Designing with classes

Composition, an alternative to inheritance

Inheritance means is a. Composition means has a.

Python
class Engine:
    def start(self):
        return "Engine running"


class Car:
    def __init__(self, make):
        self.make = make
        self.engine = Engine()      # a Car HAS an Engine

    def start(self):
        return f"{self.make}: {self.engine.start()}"

A Car is not a kind of Engine, so inheritance would be wrong here. It has one, so composition is right.

The test

If you can say "a Dog is an Animal", inheritance fits. If you can only say "a Car has an Engine", use composition. Getting this wrong produces class hierarchies nobody can understand.

A worked example: a game

Python
class Entity:
    def __init__(self, name, x, y, health):
        self.name = name
        self.x = x
        self.y = y
        self.health = health

    @property
    def alive(self):
        return self.health > 0

    def take_damage(self, amount):
        self.health = max(0, self.health - amount)

    def update(self):
        pass


class Player(Entity):
    def __init__(self, name, x, y):
        super().__init__(name, x, y, health=100)
        self.score = 0

    def update(self):
        self.score += 1


class Enemy(Entity):
    def __init__(self, name, x, y, damage):
        super().__init__(name, x, y, health=30)
        self.damage = damage

    def update(self):
        self.x += 1        # patrol


entities = [Player("Aisha", 0, 0), Enemy("Slime", 10, 0, damage=5), Enemy("Bat", 20, 5, damage=3)]

for step in range(3):
    for e in entities:
        e.update()

entities = [e for e in entities if e.alive]
for e in entities:
    print(f"{e.name} at ({e.x}, {e.y}) with {e.health} health")

One loop updates everything, whatever it is. That is the pattern behind every game engine you have ever used.

The four pillars, summarised

PillarMeaning
AbstractionExposing only what is needed and hiding the complexity behind it
EncapsulationBundling data with its methods and controlling access to it
InheritanceA class taking on the attributes and methods of another
PolymorphismObjects of different classes responding to the same method call in their own way

Key terms you must be able to define

Inheritance
A class taking on the attributes and methods of an existing class.
Superclass
The parent class that another class inherits from.
Subclass
A class that inherits from another class.
Override
Redefining a parent's method in a subclass so the subclass version is used.
super()
A function that calls the parent class's version of a method.
Polymorphism
Objects of different classes responding to the same method call in their own way.
Abstract class
A class that cannot be instantiated and exists only to be inherited from.
Composition
Building a class by giving it other objects as attributes, expressing a has a relationship.
Abstraction
Exposing only what is needed and hiding the underlying complexity.

Flashcards

Say the definition out loud before you flip the card. Retrieving it from memory is what builds recall, reading it again is not.

DefineInheritance
AnswerA class taking on the attributes and methods of an existing class.
DefineSuperclass
AnswerThe parent class that another class inherits from.
DefineSubclass
AnswerA class that inherits from another class.
DefineOverride
AnswerRedefining a parent's method in a subclass so the subclass version is used.
Definesuper()
AnswerA function that calls the parent class's version of a method.
DefinePolymorphism
AnswerObjects of different classes responding to the same method call in their own way.
DefineAbstract class
AnswerA class that cannot be instantiated and exists only to be inherited from.
DefineComposition
AnswerBuilding a class by giving it other objects as attributes, expressing a has a relationship.

Reach the top grade on this topic

  • Explain inheritance in terms of code reuse and a single place to fix shared behaviour
  • Explain polymorphism with the argument about not needing to edit existing loops
  • Use super() to extend a parent method rather than duplicating its code
  • Choose inheritance for is a relationships and composition for has a
  • Be able to name and explain all four pillars with a concrete example of each

Mistakes that cost marks here

Avoid these
  • Forgetting to call super().__init__() so the parent's attributes are never set up.
  • Using inheritance for a has a relationship, producing hierarchies that make no sense.
  • Copying the parent's code into the subclass instead of calling super().
  • Writing isinstance checks everywhere, which is exactly what polymorphism is meant to remove.
  • Creating a deep chain of subclasses when one or two levels would do.
Fun fact

Polymorphism means many forms. It is what allows a game to loop through a list containing players, enemies, bullets and explosions and simply call update() on each, without ever asking what any of them actually is.

Knowledge check

0 of 10 answered
1What does inheritance allow?
2What does super().__init__(name, age) do?
3What is overriding?
4What is polymorphism?
5A Car and an Engine are best related by:
6What happens if a subclass does not define a method the parent has?
7Why is polymorphism useful when adding a new type of object to a game?
8What is an abstract class?
9Which is NOT one of the four pillars of OOP?
10What is the main benefit of putting shared behaviour in a parent class?
Score 0 / 10

Exam-style questions

Five written questions in the style of the real paper. Write a full answer, then let the marker check it against the mark scheme.

23 marks
1Explain what is meant by inheritance in object oriented programming.3 marks
Explain

Mark scheme0 / 3
  • LA new class is based on an existing class
  • LIt takes on the attributes and methods of that class
  • LIt can add new members or override existing ones
Model answer

Inheritance is a relationship in which a new class, the subclass, is based on an existing class, the superclass. The subclass automatically takes on all the attributes and methods defined in the superclass, so shared behaviour only has to be written once. The subclass can then add attributes and methods of its own, and it can override any inherited method by defining its own version, which is then used in place of the parent's when called on a subclass object.

The marker looks for key phrases. If you said it another way and it is still right, give yourself the mark.
2Explain what polymorphism is and why it is useful, using an example.4 marks
Explain

Mark scheme0 / 4
  • LObjects of different classes respond to the same method call
  • LEach provides its own implementation of that method
  • LGives a valid example such as a list of shapes or animals
  • LExplains that existing code does not need changing when a new class is added
Model answer

Polymorphism means that objects of different classes can be used through the same interface, with each one responding to a method call in its own way. For example, a list might contain Circle, Square and Triangle objects, all inheriting from a Shape class, and a loop can simply call area() on each without ever asking what type it is holding, because each class provides its own version of the method. This is useful because it removes the need for long chains of type checks. Without polymorphism, every loop that processed shapes would need an if statement for each type, and adding a new shape would mean finding and editing every one of those loops. With polymorphism, a new shape is added by writing one new class, and all the existing code continues to work unchanged.

The marker looks for key phrases. If you said it another way and it is still right, give yourself the mark.
3A program has an Animal class with a speak method. Write a Dog subclass that overrides speak, and explain what overriding means.4 marks
Write

Mark scheme0 / 4
  • LDefines the subclass inheriting from Animal
  • LDefines a speak method with the same name
  • LReturns or prints a dog specific sound
  • LExplains that the subclass version replaces the parent version for objects of that class
Model answer

class Dog(Animal): def speak(self): return "Woof" Overriding means defining a method in a subclass that has the same name as one in its parent class. When that method is called on an object of the subclass, Python uses the subclass version rather than the inherited one, so a Dog object returns Woof while another Animal subclass returns something different. The parent's version is not removed and can still be reached using super().speak() if the subclass wants to extend the original behaviour rather than replace it entirely.

The marker looks for key phrases. If you said it another way and it is still right, give yourself the mark.
4Explain the difference between inheritance and composition, and state when each should be used.4 marks
Explain

Mark scheme0 / 4
  • LInheritance expresses an is a relationship
  • LComposition expresses a has a relationship, where an object contains another
  • LUse inheritance when the subclass genuinely is a kind of the parent
  • LUse composition when one object simply uses or contains another
Model answer

Inheritance expresses an is a relationship: a Dog is a kind of Animal, so making Dog a subclass of Animal makes sense and Dog inherits everything an Animal can do. Composition expresses a has a relationship, where one object holds another as an attribute: a Car has an Engine, so the Car class creates an Engine object and stores it. The test for which to use is whether the sentence with is a makes sense. A Car is not a kind of Engine, so inheriting from Engine would produce a class hierarchy that is confusing and would give Car methods that make no sense for it. Choosing the wrong one is a common source of class hierarchies that nobody can follow, so the general advice is to prefer composition unless the is a relationship is genuinely true.

The marker looks for key phrases. If you said it another way and it is still right, give yourself the mark.
5Name and briefly explain the four principles of object oriented programming.8 marks
Name

Mark scheme0 / 8
  • LAbstraction is exposing only what is needed
  • LAbstraction hides underlying complexity from the user of a class
  • LEncapsulation is bundling data with the methods that act on it
  • LEncapsulation controls access so data stays valid
  • LInheritance is a class taking on the attributes and methods of another
  • LInheritance avoids duplication because shared code is written once
  • LPolymorphism is different classes responding to the same call in their own way
  • LPolymorphism means new classes can be added without changing existing code
Model answer

Abstraction means exposing only what is needed to use a class while hiding the complexity behind it. Someone using a list does not need to know how it stores items in memory, only that append adds one, and a well designed class works the same way. Encapsulation means bundling an object's data together with the methods that operate on it, and controlling how that data can be accessed from outside. Because changes go through the class's own methods, it can validate them and guarantee that the object never enters an invalid state, such as a bank account with a negative balance. Inheritance means a class can be built from an existing one, automatically taking on its attributes and methods and then adding or overriding whatever it needs. This avoids duplication, because behaviour shared by many classes is written once in the parent, and a correction made there applies everywhere at once. Polymorphism means that objects of different classes can respond to the same method call, each in its own way. A loop can call update on every object in a list without knowing or caring what type each one is, and because of this a new type can be added simply by writing a new class, with no change at all to the code that already uses them. Together these four principles produce code that is easier to reuse, easier to extend and far easier to keep correct as it grows.

The marker looks for key phrases. If you said it another way and it is still right, give yourself the mark.
Machine mark 0 / 23