Inheritance, polymorphism and overriding
Subclasses and super, overriding, polymorphism, and abstract, virtual and static methods, with robot behaviours as subclasses.
Do this lesson in the simulatorA 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
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()
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())
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
virtualbefore 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.areaabove is abstract by convention: it raisesNotImplementedErrorif a subclass forgets to override it. Python'sabcmodule 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
selfand is called on the class, as inMotor.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))
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.
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): namechirp; storesnote(a frequency in Hz) and itsactplays that note twice, 0.2 seconds each time.Square(size): namesquare; storessize(cm) and itsactdrives a square with sides ofsizecm, turning right 90 degrees at each corner.Spin(): namespin; itsactturns 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
- Add a
Pause(seconds)subclass that does not overrideact. What does it print, and why? - Make
Behaviourabstract with Python'sabcmodule and@abstractmethod. What happens now when you tryBehaviour("test")? - Add a
Polygon(sides, size)subclass, then makeSquarea subclass ofPolygon. How much ofSquareis left?