Skip to the solution
MediumDSA13 min read

What is a generator's lifecycle?

170 views

The problem

Question presented to candidate: "A generator object goes through several states between the moment you call the function and the moment it is gone. Walk me through them. What moves it from one state to the next, and what happens to code in a finally block if the consumer stops early?"

What a strong answer should cover:

  • The four states reported by inspect.getgeneratorstate: GEN_CREATED (body not started), GEN_RUNNING (executing right now), GEN_SUSPENDED (paused at a yield), GEN_CLOSED (finished); a generator never comes back from closed
  • What moves it: next() / send(value) resume it (and send must be None on a just-created generator); yield suspends it; return or an uncaught exception closes it, with the return value carried on StopIteration.value
  • throw(exc) raises an exception at the paused yield; close() raises GeneratorExit there, so finally blocks and with exits run; a generator that yields again after GeneratorExit gets RuntimeError
  • Dropping the last reference to a suspended generator closes it as well (in CPython this happens immediately through reference counting); break out of a for loop leaves it suspended, not closed
  • PEP 479 (default since 3.7): a StopIteration escaping the body is turned into RuntimeError; version details such as the three-argument throw() being deprecated in 3.12, gi_suspended (3.11) and close() returning a value (3.13)
  • Introspection: gi_frame (None once closed), gi_running, gi_frame.f_locals

Code / implementation expected: Optional โ€” a generator with a try/finally, printing inspect.getgeneratorstate before, during and after, and calling close().

  • generators
  • lifecycle

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

13 min read ยท 10 sections

Run the code

Edit the code and run it here. Each console line with an expected value becomes a test.

PythonThe four states, send(), return values and gi_frame
Pythonclose(), del, throw(), GeneratorExit and PEP 479
PythonDeprecated three-argument throw() and close() returning a value (Python 3.13)
import sys
import warnings

print("Python", sys.version_info[:2])

def gen():
    try:
        yield 1
    except ValueError as e:
        yield f"caught {e}"

# The one-argument form: pass an exception instance.
g = gen()
next(g)
print("one-arg throw ->", g.throw(ValueError("boom")))

# The old three-argument form still works but warns on 3.12+.
g = gen()
next(g)
with warnings.catch_warnings(record=True) as caught:
    warnings.simplefilter("always")
    result = g.throw(ValueError, ValueError("old style"), None)
print("three-arg throw ->", result)
for w in caught:
    print(w.category.__name__ + ":", w.message)

# Since 3.13, close() returns the value a generator returns
# after catching GeneratorExit (on 3.12 and earlier it returns None).
def summer():
    total = 0
    try:
        while True:
            total += yield
    except GeneratorExit:
        return total

s = summer()
next(s)
for n in (5, 10, 20):
    s.send(n)
print("close() returned:", s.close())

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 105 of 139 in Python

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Python questions
Up nextMedium

How does a Python list grow in memory?

Continue