Iteration as an Interface · practice
Make a Domain Object Iterable
Chapter 6 gave Quiz a
for question in quiz.questions():
print(question.prompt)
That remains useful when a caller needs a
for question in quiz:
print(question.prompt)
Delegating to something already iterable
Quiz holds a list, and a list already knows how to be walked. So __iter__ has nothing to invent:
One line, and no __next__ anywhere. iter(self._questions) asks the list for a fresh
This is what you write in practice. The two-class version from Lesson 3 exists so you can see what iter() on that list is doing; almost every real __iter__ is this one line.
What it buys
The gain is not that for question in quiz is three characters shorter. It is that Quiz now works with everything that speaks the iteration protocol, without any of it knowing what a Quiz is:
sorted, in, enumerate, zip, min, max, sum, any, all, and Quiz in mind. They ask for an iterator, and Quiz answers. The items still need to suit the operation: this quiz contains sum.
This is the chapter’s title. Iteration is an interface: implement one method and a large part of the language starts accepting your object.
Why can a Quiz whose __iter__ returns iter(self._questions) be looped over twice, when the Countdown from the previous lesson could not?
Handing out a copy, revisited
Chapter 6 was careful to return list(self._questions) so a caller could not empty the quiz through the list it was given. Does __iter__ reopen that hole?
Not through the iterator itself. An iterator lets a caller receive items one at a time, and the protocol offers no operation for adding or removing entries from the bank. The yielded objects may still be for question in quiz protects the collection’s structure rather than promising deep read-only access.
That makes __iter__ a better answer here than questions(): it exposes one natural stream of questions without handing out a list to measure, questions() method only if a caller genuinely needs that sequence-like interface.
Choosing what to iterate
An object with several collections has to decide which one for x in obj means, and the answer should be the obvious one. A Quiz iterates its questions. A Gradebook iterates its records. If there is no obvious choice, do not define __iter__ at all: give the collections names, and let callers write for result in book.records().
__iter__ promises a traversal of one particular kind of item. It does not promise indexing or a length; those are separate sequence operations.
Task
Make both classes
Quiz.__iter__ walks its questions. Gradebook.__iter__ walks its records. Each is one line, and neither needs a __next__.
Both must stay re-iterable: looping twice sees everything twice.
Then write two
total_points(quiz)sums the points of every question, using the quiz directly;top_record(book)returns the record with the highest percentage, orNonewhen the gradebook is empty.
Neither may call .questions() or .records(). The grader passes them an ordinary Quiz and a Gradebook, which only works if they ask for