Inheritance and Polymorphism · practice
Override and Extend Behavior
Each subclass in the last lesson supplied its own is_correct. That is overriding: a subclass defines a
There is a second thing a subclass can do, and the difference matters.
Replace, or add to
Overriding describe replaces the base version completely:
The point
Often what you want is the base version plus something:
super().describe() calls the base version from inside the override, and the subclass adds to what comes back. The points reappear, and they will keep appearing if the base changes how it words them.
This is the same super() as in __init__, doing the same job: run the base’s version of this method. __init__ is simply the case you meet first.
A subclass's describe calls super().describe() and appends to the result. What does that buy over rewriting the whole string?
Where each method comes from
With a family of classes, “which version runs?” needs an answer you can rely on. Python looks at the
summary is defined only on Question, and it calls self.describe(). The version that runs is TextQuestion’s, because self is a TextQuestion and the search starts there.
This is worth pausing on, because it is what makes a base class useful beyond sharing code. Question.summary was written without knowing what kinds of question would ever exist, and it still calls the right describe for each one. A base class can define the shape of an operation and leave the varying part to its subclasses.
Overriding responsibly
Two rules keep an override from surprising the code that calls it.
Accept what the base accepts. If Question.is_correct(response) takes one
Return what the base returns. If is_correct returns True or False, a subclass returning "yes", or a number of points, or None for “unanswered”, will pass through if question.is_correct(...) and behave in ways nobody predicted.
No error. Just Summary: None reaching a screen somewhere. The next lesson but one is entirely about this class of problem.
Extending state as well as behavior
__init__ follows the same pattern, and now you can see it as one case of a general rule:
def __init__(self, prompt, points, tolerance):
super().__init__(prompt, points) # what every question needs
self.tolerance = tolerance # what this kind adds
Call the base first, then add. Doing your own setup before calling super().__init__ is legal and occasionally necessary, but it means part of the object exists before the base has finished, which is the Chapter 5 problem of a half-built object, arriving through a new door.
Task
Each subclass here rewrites the whole description, so a change to the base wording would never reach any of them.
Make all three extend the base description instead of replacing it. Each describe() should call super().describe() and add its own part:
TextQuestion:"<base> Type your answer."ChoiceQuestion:"<base> Options: A, B, C."listing its own optionsNumericQuestion:"<base> Answer within 0.2."using its own tolerance
The base wording is "<prompt> (<points> points)", and none of the subclasses may contain that format. The grader changes the base wording and checks that all three follow, which is the whole reason to do it this way.
Then add summary() to the base only, returning "Summary: <description>". Written once, it must produce the right description for each kind.