0%

Iteration as an Interface · practice

Pause Work with yield

Lesson 3 needed twenty lines and two classes to walk a . Here is the same thing:

Try it

yield instead of , and that one word turns an ordinary into something that produces the whole protocol for you.

What yield does

A function containing yield is a generator function. Calling it does not run the body:

Try it

It returns a generator , and that object is an iterator: it has __iter__ and __next__, it can be passed to for, list, sum, and it is used up after one pass.

A generator returns one value at each next call and pauses at yield until the next call.

The body runs only when something asks for an item, and then it runs until the next yield and stops there:

Try it

Read that output carefully. “starting” does not print until the first next(). “between” prints during the second. The function is not running and then returning; it is pausing, keeping everything it had, and resuming where it left off.

When the function ends, the generator raises StopIteration on its own. You never write that.

What happens when you call a generator function?

Local state, kept for free

The reason this replaces the Lesson 3 machinery is that the pause keeps your . The position you had to store in self._index is now just a place in the code:

Try it

total survives across pauses without being an attribute of anything. Compare that with writing RunningTotalIterator with __init__, __iter__, __next__, a stored , and a stored total, and you have the for generators in one example.

Iterating without a list underneath

A generator does not need a collection to walk. It can produce values that never all exist:

Try it

There is no list of labels anywhere. Each one is built when asked for and discarded after use, which is the “things too big to hold” idea from Lesson 1, now something you can write.

Making a class iterable with yield

__iter__ may be a generator function, which is the tidiest way to iterate something that is not just a stored list:

Try it

Still re-, because each call to __iter__ starts a new generator with its own position. When __iter__ needs to filter, transform, or flatten, this is what to write. When it just hands over a stored collection, return iter(self._questions) from Lesson 4 is shorter and says so.

One thing to watch

A generator is used up like any iterator, and the mistake is easy to make:

Try it

The second sum gets nothing. If you need the values twice, build a list once and reuse it. Lesson 6 is about deciding which result shape you want, and how to stop a lazy source at an exact boundary.

Task

Implement one generator: passing(records, mark) yields each record whose percentage is at least mark, in input order.

Calling passing must not inspect a record yet. It should start only when the caller asks for a result, and it must stay lazy enough that a caller can take one passing record from an endless input.

The supplied running_total and numbered demonstrate accumulated state and transformation. The supplied QuestionBank.__iter__ demonstrates a re- generator . Read and run them, but leave them unchanged.