Skip to content
Lesson 4 of 9

Step 1 of 7 · Reading · ~4 min

Learn

Generators and Iterators

Generators and yield

You will be able to write functions that hand back values one at a time, chain them into pipelines that read a stream without holding it in memory, and say what a for loop does underneath.

In Python in Practice, for walked lists, strings, dicts and sys.stdin, and map and filter gave back objects you could use only once. They all share one protocol, which you can now write yourself.

The iterator protocol

iter(x) asks an iterable for an iterator; next(it) asks the iterator for its next value and raises StopIteration when none is left. A for loop is those two calls inside a try/except StopIteration.

python

A list is iterable but not an iterator: each iter(lst) makes a fresh one. An iterator returns itself from iter(). A class can implement the protocol by hand:

python

Generator functions

A function containing yield is a generator function. The same countdown, without the bookkeeping:

python

Calling it runs nothing and returns a generator object. Each next() runs the body to the next yield, hands out that value and freezes, locals included:

python

next(g, default) returns the default instead of raising once the body finishes. return ends a generator early; raise StopIteration inside one becomes RuntimeError.

One pass, shared position

A generator is its own iterator, so it is spent after one pass, silently (sum(g) would now be 0):

python

Two loops over one iterator split the items between them, and break consumes the item it stopped on:

python

On purpose, header = next(rows) takes the first item and a loop over rows sees the rest.

Generators as __iter__, and yield from

A generator function can serve as __iter__, and yield from delegates to another iterable:

python

Each iter(s) starts a fresh generator, so s loops repeatedly; Countdown returned self and cannot.

Infinite streams and pipelines

A generator may never finish. Bound it with itertools.islice:

python

Generators that consume generators form a pipeline; each item travels the whole chain before the next is pulled:

python

Given lines INFO: boot, ERROR: disk full, INFO: ok, ERROR: no route, it prints disk full, no route. Memory use is one line, however long the input. (send() can also push values into a generator; that is coroutine territory.)

Common mistakes

  • Reading state before the stream has run. A counter updated inside a generator is 0 until something pulls items through it. Print it after the loop.
  • Looping twice over a spent generator. The second pass yields nothing and raises nothing, so a total comes out as 0.
  • Peeking with for ... break. The item you broke on is gone; use next() to take exactly one.

Your exercise

Write two generators, one that yields parsed integers and one that consumes it, and let the starter's loop pull everything through. The classic slip is reading the integer count before the loop has finished.

Up nextGenerator ExpressionsGenerators and Iterators

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…