Iterators & Generators
A track of P29 · Functions, Decorators & Generators.
Rebuild the iterator protocol, replace it with yield, and exploit laziness on endless streams; then contextlib context managers and send() open the doorway from generators to coroutines and P33's async world.
Here is a task that breaks the obvious solution: compute a sliding average
over a log stream that never ends. list(stream) never returns — the input
is infinite. Yet the working code is four lines with a yield in them, and it
handles ten items or ten billion in the same constant memory. The difference
between those two programs — materialise everything, or produce values on
demand — is this track's subject.
It starts by rebuilding what every for loop does: call iter() once, then
next() until StopIteration. You implement the protocol by hand
(__iter__, __next__, staying exhausted once done) so it is machinery, not
folklore — and then replace the whole class with a generator function, where
yield suspends the frame, locals and position preserved, and resumes on the
next pull. Laziness follows for free: a paginator written with yield serves
its first page from an endless itertools.count() at the same cost as from a
seven-item string, and small lazy tools chain into constant-memory pipelines
— a sliding window over a filter over a counter, pulled one step at a time.
The closing module is the pillar's hand-off. contextlib.contextmanager
turns a one-yield generator into a with block — setup above the yield,
guaranteed teardown below, rollback in a try/except around it. Then the
doorway: yield is an expression, gen.send(x) resumes the frame with a
value, and a generator you drive that way is already a coroutine. That is
the lineage async/await gave dedicated syntax to, and P33 — Asyncio &
Coroutines — walks through exactly this door when it arrives.