Iterators
An iterator hands out values one at a time. Each next() call returns the next value; when none are left, it raises StopIteration. A for loop is built on this: it calls next() for you until StopIteration.
A def whose body contains yield is a generator function. Calling it does not run the body. It returns a generator: an iterator that runs the body a little at a time, each time you ask for a value.
Inside a generator
countdown(3) builds a generator and = stores it as gen. Its frame is ready, with n = 3, set to start at line 2. But no line of the body has run yet. Its state is created.
next(gen) wakes the body up for the first time. It runs from the top: line 2 checks n > 0. 3 > 0 is True, so the loop body runs, starting with line 3.
yield n sends 3 back as the value of next(gen), and print() shows it. The generator then pauses on that line, keeping n and its place: next time it carries on at line 4. Its state is suspended.
The second next(gen) does not start over. It resumes after the yield: line 4 makes n 2, line 2 checks 2 > 0, and line 3 yields 2. print() shows 2, and the generator pauses again.
for x in gen first calls iter(gen), which hands back gen itself. Then it calls next() on each pass. The body resumes, n becomes 1 and line 3 yields 1. x takes it, and line 9 prints 1.
The end
The loop asks once more. n becomes 0, so n > 0 is False and the body ends. The generator raises StopIteration, which the for loop catches: the loop simply stops, with no error. The state is closed.
The generator never held 3, 2 and 1 at once: it made each value only when asked. That is lazy evaluation. sum(x * x for x in range(10**6)) adds a million squares the same way, without ever building a list.
Try it yourself
;; step 1/9 >>> it = iter([10, 20]) # a list gives an iterator >>> next(it) 10 >>> next(it) 20 >>> next(it) Traceback (most recent call last): ... StopIteration
The "next line" box is a picture of what Python keeps for a paused generator: its local variables and where in the body to carry on. While a generator is suspended, gen.gi_frame.f_lineno reports the yield line itself (line 3); it carries on with the line after it. A generator runs only once. When it is finished, every further next() raises StopIteration at once, and a for loop over it does nothing. Call countdown(3) again to get a fresh one.