0%

Inheritance and Polymorphism · practice

Use Polymorphic Collections

A quiz holds several kinds of question at once. Chapter 6 happened to give every Question the same behavior; Python themselves never required that. Now the shared operation matters more than the concrete class:

Try it

Quiz is unchanged from Chapter 6. It never mentions TextQuestion or ChoiceQuestion, and it does not need to: it holds questions, and every question can describe itself and report its points.

A collection whose items are of different classes used through one shared set of operations is a polymorphic collection. Most of the of the last four lessons shows up here.

One is_correct call can dispatch to different Question subclasses with their own behavior.

Scoring a mixed quiz

Try it

Nine lines that would have been a growing chain of type checks. Adding a numeric question changes nothing here, in Quiz, or in the report.

What has to change in Quiz and score to support a fourth kind of question?

Sorting and filtering a mixed collection

Chapter 7’s tools work here unchanged, and they are worth seeing on a mixed list because it is where they earn their place:

Try it

Both read points, which every question has. Neither knows or cares that different classes are present.

The temptation to peek

Eventually something genuinely seems to need the type. A report might want to count how many choice questions a quiz contains:

choice_count = 0
for question in quiz.questions():
    if isinstance(question, ChoiceQuestion):
        choice_count = choice_count + 1

This works, and it is the type chain from Lesson 1 coming back in a new coat. The class list has moved into the report, and an unrelated class offering the same choice behavior would be missed. Subclasses of ChoiceQuestion would still pass this check.

Usually the honest fix is to ask the something:

Try it

The knowledge of what each class is stays with the class. Adding a new kind of choice question is a matter of what it reports about itself, not of finding every isinstance in the program.

This is not an absolute rule. isinstance is fine at a genuine boundary, such as validating something that arrived from outside your program. Inside a family you designed, it usually means an operation is missing from the base.

The exercise builds a quiz holding four kinds of question and a report over it, with no type checks anywhere.

Task

The validated four-kind question family and the safe Quiz collection are supplied. Build collection operations that never ask which concrete kind they received.

Complete three :

  • graded_questions(quiz) returns the questions worth more than zero;

  • count_of_kind(quiz, label) returns how many questions report that label;

  • report_lines(quiz, responses) returns one "<description>: correct" or "<description>: incorrect" line per question, then "Score: <points> of <total>".

None of the three may use isinstance or name any subclass. The grader adds a fifth kind of question it invents itself and expects all three to handle it.