Skip to main content

Generators and Coroutines

Generators and coroutines are implemented using specialized frame objects that can suspend and resume execution.

Generators

Generators in CPython are implemented with the PyGenObject struct, which consists of an embedded frame and metadata.

Generator Structure

Defined as _PyGenObject_HEAD in Include/internal/pycore_interpframe_structs.h:

Frame Embedding

The frame is embedded in the generator object:
  • Allocated as a single memory block with the generator
  • Can be accessed bidirectionally:
    • Generator → Frame: Direct struct member
    • Frame → Generator: _PyGen_GetGeneratorFromFrame() in pycore_genobject.h

Generator Lifecycle

Creation

Generator functions compile to bytecode that starts with RETURN_GENERATOR:
When RETURN_GENERATOR executes:
  1. Create PyGenObject with embedded frame
  2. Copy current frame state to embedded frame
  3. Set owner field to indicate generator ownership
  4. Push generator object to stack
  5. Return to caller (destroying current frame)

Execution

When .send() is called on a generator:
  1. gen_send_ex2() in Objects/genobject.c is invoked
  2. Generator’s frame is pushed onto call stack
  3. _PyEval_EvalFrame() resumes execution
  4. Execution continues from last yield point

Yielding

The YIELD_VALUE instruction:
  1. Puts value on stack for caller
  2. Updates frame’s instruction pointer
  3. Saves interpreter exception state to generator
  4. Returns execution to calling frame
  5. Leaves generator frame ready to resume

Destruction

In gen_dealloc() (Objects/genobject.c):
  1. Check if frame is exposed as PyFrameObject
  2. If exposed and has refcount > 1, call take_ownership()
  3. take_ownership() copies frame to the frame object
  4. Otherwise, clear frame and deallocate generator
Defined in Python/frame.c.

Iteration

FOR_ITER Instruction

The FOR_ITER instruction calls __next__() on the iterator:

FOR_ITER_GEN Specialization

The specialized FOR_ITER_GEN instruction:
  • Detects when iterating over a generator
  • Bypasses __next__() call overhead
  • Directly pushes generator frame and resumes execution
  • Significantly faster than generic FOR_ITER

Chained Generators (yield from)

The yield from expression efficiently chains generators:

SEND Instruction

Implements yield from logic:
  1. Push value onto chained generator’s stack
  2. Set exception state on generator’s frame
  3. Resume chained generator execution
  4. On return, yield value up the chain with YIELD_VALUE

Loop Structure

CLEANUP_THROW Instruction

Handles exceptions in the send-yield loop:
  • StopIteration: Extract value field, return from generator
  • Other exceptions: Re-raise
Defined in Python/bytecodes.c.

Coroutines

Coroutines are generators that can receive values via .send():

Send Value Flow

Data flows bidirectionally:
  1. Generator → Caller: Value passed to yield expression
  2. Caller → Generator: Argument to .send() call

Implementation

Both generators and coroutines use the same mechanism:
  • __next__() simply calls self.send(None)
  • send() is implemented in gen_send_ex2() (Objects/genobject.c)
  • Send argument becomes the value of the yield expression

Yield From with Send

The SEND instruction passes the send argument down the generator chain:

Coroutine Types

CPython has three coroutine-like types:

Generator-based Coroutines

Created with @types.coroutine or asyncio.coroutine:

Native Coroutines

Defined with async def:

Asynchronous Generators

Combine async def with yield:
All three share the same underlying implementation with different type flags.

Generator State

Generators track their execution state:

State Values

  • GEN_CREATED - Just created, not started
  • GEN_RUNNING - Currently executing
  • GEN_SUSPENDED - Yielded, can be resumed
  • GEN_CLOSED - Finished or closed

State Transitions

Detecting Reentrancy

State checking prevents reentrant generator execution.

Generator Methods

.send(value)

Resume with a value:

.throw(exc)

Inject exception at yield point:

.close()

Terminate generator:

Example: Generator Inspection

Performance Characteristics

Memory Efficiency

Generators use less memory than lists:

Execution Overhead

Per-iteration overhead:
  • List iteration: ~50 ns/iteration
  • Generator iteration: ~100 ns/iteration
  • Specialized FOR_ITER_GEN: ~75 ns/iteration
Generators trade slightly higher per-item cost for much better memory usage.