Inheritance, polymorphism and overriding

Subclasses and super, overriding, polymorphism, and abstract, virtual and static methods, with robot behaviours as subclasses.

A1.8Programming techniques and object-oriented programmingA level25 min

Do this lesson in the simulator

A robot has many behaviours: chirp, drive a square, spin, follow a wall. They are different, but they have things in common: each has a name and each can be told to act. Inheritance lets the common part be written once, in one class, and shared by every behaviour. Polymorphism then lets a program tell any behaviour to act without knowing which kind it is.

Inheritance

A class can inherit from another. The new class, the subclass (child, or derived class), gets all the attributes and methods of the superclass (parent, or base class), and adds its own. Inheritance models an "is a" relationship: a chirp is a behaviour.

# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()

class Behaviour:
    def __init__(self, name):
        self.name = name

    def describe(self):
        return "behaviour " + self.name

class Chirp(Behaviour):                   # Chirp inherits from Behaviour
    def __init__(self, note):
        super().__init__("chirp")         # run the superclass constructor first
        self.note = note                  # then add what only a Chirp has

    def act(self):
        tone(self.note, 0.2)

c = Chirp(523)
print(c.describe())                       # inherited from Behaviour
c.act()                                   # defined in Chirp

Run this in the simulator

Chirp never defines describe, but has it, because it inherits it. super().__init__("chirp") calls the superclass's constructor so the name attribute is set up in one place, not copied into every subclass.

Overriding

A subclass overrides a method when it defines a method with the same name as one it inherits. Its own version is used instead of the superclass's. The superclass's version can still be called with super():

class Behaviour:
    def __init__(self, name):
        self.name = name

    def act(self):
        print(self.name, "does nothing")

class Wait(Behaviour):
    def __init__(self):
        super().__init__("wait")

class Report(Behaviour):
    def __init__(self):
        super().__init__("report")

    def act(self):                        # overrides Behaviour.act
        super().act()                     # the superclass's version first
        print(self.name, "then adds a line of its own")

Wait().act()
Report().act()

Run this in the simulator

Polymorphism

Polymorphism means "many forms": the same method call behaves differently depending on the class of the object it is called on. A program can hold objects of different subclasses in one list and call the same method on each. Which version runs is decided when the program runs, by the object's class:

class Shape:
    def area(self):
        raise NotImplementedError

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

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

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

for shape in [Rectangle(20, 30), Circle(10), Rectangle(5, 5)]:
    print(type(shape).__name__, shape.area())

Run this in the simulator

The loop never tests which class shape is. Adding a Triangle class later needs no change to the loop at all, which is the point: code written for the superclass works for subclasses that did not exist when it was written.

Abstract, virtual and static methods

  • A virtual method is one that a subclass may override. In Python every method is virtual; in C++ and C# a method must be marked virtual before it can be overridden.
  • An abstract method has no body in the superclass and must be overridden. A class with an abstract method is an abstract class, and cannot be instantiated. Shape.area above is abstract by convention: it raises NotImplementedError if a subclass forgets to override it. Python's abc module enforces it properly, refusing to create an object of a class with an unimplemented abstract method.
  • A static method belongs to the class rather than to an object. It has no self and is called on the class, as in Motor.clamp(120); it cannot use any object's attributes.
class Motor:
    @staticmethod
    def clamp(speed):
        return max(0, min(100, speed))

print(Motor.clamp(120), Motor.clamp(-5), Motor.clamp(60))

Run this in the simulator

Drawing inheritance

In a class diagram, inheritance is a line from the subclass to the superclass with a hollow triangle at the superclass end. Inherited attributes and methods are not repeated in the subclass box; an overridden method is shown again.

Chirp, Square and Spin inherit from BehaviourBehaviour+ name : String+ act()Chirp+ note : Integer+ act()Square+ size : Integer+ act()Spin+ act()
The class diagram for the task: three subclasses each override act()

OCR's exam reference language writes inheritance with inherits, and calls the superclass's methods with super.:

class Chirp inherits Behaviour
    private note

    public procedure new(givenNote)
        super.new("chirp")
        note = givenNote
    endprocedure
endclass

Task: behaviours by inheritance

The starter has the superclass Behaviour, with a name attribute and an act() method. Write three subclasses from the class diagram. Each constructor calls super().__init__ with the behaviour's name, and each overrides act() so that it does its job and then prints <name> done:

  • Chirp(note): name chirp; stores note (a frequency in Hz) and its act plays that note twice, 0.2 seconds each time.
  • Square(size): name square; stores size (cm) and its act drives a square with sides of size cm, turning right 90 degrees at each corner.
  • Spin(): name spin; its act turns right a full 360 degrees.

Then make the list routine = [Chirp(523), Square(20), Spin(), Chirp(784)] and call act() on each object in order with one loop. Do not use isinstance or type: polymorphism chooses the method.

# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()

class Behaviour:
    def __init__(self, name):
        self.name = name

    def act(self):
        print(self.name, "does nothing")

routine = [Behaviour("chirp"), Behaviour("square")]
for behaviour in routine:
    behaviour.act()

Challenges

  1. Add a Pause(seconds) subclass that does not override act. What does it print, and why?
  2. Make Behaviour abstract with Python's abc module and @abstractmethod. What happens now when you try Behaviour("test")?
  3. Add a Polygon(sides, size) subclass, then make Square a subclass of Polygon. How much of Square is left?