Generator Functions & yield
Suspend execution states, stream infinite sequences lazily, and delegate streams with yield from.
yield Turns a Function Into a Pausable, Resumable Stream
A function containing yield anywhere in its body compiles to a generator factory. Calling it doesn't execute a single statement — it just returns a suspended generator object.
Each call to next(gen) runs bytecode until it hits a yield expression, returns that value, and freezes the frame — locals, instruction pointer, everything — until the next call resumes it exactly where it left off.
return [i * i for i in range(n)]
for i in range(n):
yield i * i
yield is like pressing pause on a movie projector: it hands you the current frame, remembers its exact spot on the reel, and waits patiently until you press play again with the next next() call.
Tracing Generator State Suspension
Run the snippet below to see local state survive across multiple yield pauses:
def fibonacci_stream(limit):
a, b = 0, 1
count = 0
while count < limit:
yield a
a, b = b, a + b
count += 1
# Creating the suspended generator object
fib = fibonacci_stream(5)
print("Generator object created:", fib)
# Step-by-step resumption
print("1st:", next(fib))
print("2nd:", next(fib))
print("Remaining:", list(fib))Pauses execution, returns a value to the caller, and freezes the frame until resumed.
Resumes the generator and injects value as the result of the paused yield expression.
Forwards iteration and the send/throw/close channel directly to a subgenerator.
Raises GeneratorExit at the suspension point, guaranteeing cleanup via finally blocks.
Trace a Generator From Birth to Teardown
Click through each lifecycle phase to see exactly what happens under the hood:
Calling the generator function returns a suspended generator object immediately — no code runs until next() is called, and local state is frozen in the frame between calls.
Creation, Communication, and Lifecycle
These three families cover every generator pattern you'll write in real pipelines.
Creation & suspension
def f(): yield xAny function containing yield becomes a generator factory — calling it returns a suspended generator object, running no code yet.
next(gen)Resumes execution until the next yield, returning its value; raises StopIteration when the function ends.
Bi-directional flow
val = yield exprYields expr to the caller and pauses, later resuming with whatever value the caller sends in.
gen.send(value)Resumes the generator, injecting value as the result of the paused yield expression.
yield from subgen()Delegates iteration to a subgenerator, forwarding send()/throw()/close() and capturing its return value.
Lifecycle control
gen.close()Raises GeneratorExit at the suspension point so finally blocks can clean up resources.
gen.throw(Exc, val)Injects an exception at the suspension point, letting the generator handle recoverable errors internally.
Worked Hands-on Walkthroughs
Progress step-by-step from frame suspension through bi-directional communication to lifecycle cleanup:
Tracing Generator State Suspension
State (a, b, count) is preserved between calls without reinitializing variables on each step.
def fibonacci_stream(limit):
a, b = 0, 1
count = 0
while count < limit:
yield a
a, b = b, a + b
count += 1
# Creating the suspended generator object
fib = fibonacci_stream(5)
print("Generator object created:", fib)
# Step-by-step resumption
print("1st:", next(fib))
print("2nd:", next(fib))
print("Remaining:", list(fib))State (a, b, count) is preserved between calls without reinitializing variables on each step.
Building a Running Average Accumulator with .send()
send() passes data into the suspended yield expression while simultaneously advancing to the next yield and returning its value.
def accumulator():
total = 0.0
count = 0
current_avg = None
while True:
# Yield current average, receive incoming value
val = yield current_avg
if val is None:
break
total += val
count += 1
current_avg = round(total / count, 2)
calc = accumulator()
# 1. Prime the generator
next(calc)
# 2. Feed data points and inspect returned yields
print("Feed 10.0 -> Avg:", calc.send(10.0))
print("Feed 20.0 -> Avg:", calc.send(20.0))
print("Feed 15.0 -> Avg:", calc.send(15.0))
# 3. Terminate consumer
calc.close()send() passes data into the suspended yield expression while simultaneously advancing to the next yield and returning its value.
Resource Cleanup and Exception Handling in Generators
Calling stream.close() triggers GeneratorExit, ensuring resources are disposed even when consumers don't exhaust the stream.
def managed_stream(resource_name):
print(f"[OPEN] Resource {resource_name} acquired")
try:
for i in range(1, 100):
yield f"{resource_name}-item-{i}"
except GeneratorExit:
print(f"[CLEANUP] Generator closed early: Releasing {resource_name}")
raise # Must re-raise GeneratorExit!
finally:
print(f"[CLOSED] Resource {resource_name} safely disposed")
stream = managed_stream("SOCKET_443")
print("Read 1:", next(stream))
print("Read 2:", next(stream))
# Terminate stream prematurely
stream.close()Calling stream.close() triggers GeneratorExit, ensuring resources are disposed even when consumers don't exhaust the stream.
Common Traps & How to Avoid Them
Study the mistakes and clean fixes below before writing your own generators:
# Mistyped variable or missing closing parenthesis
result = generators___the_yield_keyword_val# Declare and verify variables before execution
val = 10
print("Defined correctly:", val)Beginners frequently encounter typos or forgotten definitions before using an identifier.
# Modifying shared state or unexpected variable shadowing
def update_state(data=[]):
data.append(1)
return datadef update_state(data=None):
if data is None:
data = []
data.append(1)
return dataDefault arguments in Python are evaluated once at function definition time, not at invocation time.
# Opening resources in a tight loop without closing or bounding memory
files = [open(f"log_{i}.txt", "w") for i in range(5000)]for i in range(5):
with open(f"log_{i}.txt", "w") as f:
f.write("Safe")Failing to release system resources leads to process termination under heavy production workloads.
Real-World Systems in Action
See how scripts, backend services, and LLM-serving pipelines all lean on generators to stream data at scale:
Local Developer Automation Tool
Individual developers and IT administrators automate everyday repetitive workflows.
A clean standalone script providing fast, local automation with zero dependencies.
def count_up_to(max_val):
n = 1
while n <= max_val:
yield n
n += 1
for num in count_up_to(3):
print("Yielded:", num)Backend API Service Integration
SaaS platforms run backend microservices processing requests concurrently.
A modular service handler integrating validation and structured responses.
# Service integration layer
def handle_request(payload: dict) -> dict:
return {"status": "success", "processed": True}High-Throughput Cloud Architecture
Large language model (LLM) serving frameworks stream completion tokens to user interfaces via generator yield pipelines.
Large language model (LLM) serving frameworks stream completion tokens to user interfaces via generator yield pipelines.
# High-resilience production pattern
import time
def process_stream(events: list) -> int:
processed = 0
for event in events:
if event:
processed += 1
return processedKey Points & Concept Review
- Calling a generator function returns a suspended generator object without executing statements.
- Local variables and frame pointers are preserved in heap memory during yield pauses.
- Bi-directional generators must be primed with next(g) or g.send(None) before sending data.
- yield from delegates iteration directly and captures subgenerator return values.
- generator.close() raises GeneratorExit, enabling guaranteed resource reclamation in finally blocks.
- Generators process unbounded streams within strict O(1) memory overhead.
Frequently Asked Questions & Reference Hub
Manually calling next() a few times on your own generator — and watching exactly which line resumes — is the fastest way to internalize frame suspension. Once you've traced one by hand, yield from pipelines stop feeling like magic.
See the exact mechanics of frame suspension and resumption.
Learn how bi-directional generators receive input values.
Understand subgenerator delegation and return-value capture.
See how generators guarantee resource cleanup on early exit.
Understand why generators scale to unbounded streams.
Compare lazy generator expressions to eager comprehensions.
You've Mastered Generators & yield!
You understand frame suspension, bi-directional communication, delegation, and lifecycle control. Now lock in your knowledge with hands-on practice.
Write a generator that yields even numbers up to a limit.
Earn experience points toward your developer rank as soon as you pass.
Bite-sized knowledge check with instant explanations for every question.
Worked example
Stream values on demand with yield.
def gen(): yield 1; yield 2 print(list(gen()))
How it works, step by step
- yield pauses and emits values one by one.
Generator Functions & yield practice
Write a generator function evens(limit) that yields even numbers from 0 up to (and including) limit. Consume it with a list comprehension up to 6 and print: Evens: [0, 2, 4, 6].
Coding Practice is Locked
Sign in to access the interactive Python editor, write and run code checks against automated tests, use hints, and earn XP.
Practice quiz
Check yourself
15 questions with explanations after submission, so you can correct course as you go.
Generator Functions & yield quiz
LockedYou must be signed in to take this quiz. Sign in to test your understanding, track your answers, and earn XP.
Lesson incomplete
Not completedCreate an ID or sign in to save lesson completion across devices.