Some Python functions need to provide values one at a time rather than return one finished result. In Python, yield gives the caller a value and pauses a generator function until the caller requests another.
I’ll show how to define and consume a generator, then trace where it pauses and resumes.
TL;DR
In Python, yield pauses a generator function after it produces a value, then lets the caller resume it later. Calling the function creates a generator iterator, and next() or a for loop asks that iterator for values one at a time.
- Use yield when a function should produce a sequence across several requests instead of returning one finished value.
- Use return to finish the generator. The next request after its last yield raises StopIteration.
- Generators are single-pass iterators. Convert to a list only when keeping every value in memory is useful.
What does yield do in Python?
A yield expression gives the caller a value and suspends the generator function at that point.
A function with yield anywhere in its body is a generator function, even if the statement sits inside a branch that is never taken.
An iterable is something Python can ask for an iterator, such as a list or an open file. The distinction matters when a function expects an iterable rather than one specific iterator.
| Operation | What the caller gets | What happens next |
|---|---|---|
| Call a generator function | A generator iterator | The body has not started yet |
| Reach yield | The yielded value | The function pauses at that expression |
| Request another value | The next yielded value | The function resumes after its previous yield |
| Reach return or the end | No further yielded value | Iteration ends with StopIteration |
A directory scan can yield candidate paths because its caller may be looking for a single match.
When is a list a better fit?
When a later step needs sorting, indexes, or a stable snapshot, collect values at that boundary. A generator does not replace a list when the task depends on list operations.
A generator expression is a parenthesized expression that creates values as it is consumed. Use one for a short transformation. A generator function is clearer when production requires branching, validation, or cleanup.
How do you use a Python generator step by step?
Define the generator before writing its consumer. Call it, inspect the returned iterator, and request values in sequence. The listing also covers a completion value, a sent message, and delegation.
Step 1: Define the generator and its consumers
Start with numbered(). It prints before its first yield. The other functions show a sent message and delegated iteration.
def numbered():
print("generator body started")
yield 10
print("resumed after 10")
yield 20
return "finished"
def part():
yield "alpha"
yield "beta"
return "part complete"
def wrapper():
result = yield from part()
yield f"subgenerator returned: {result}"
def receive_one():
value = yield "ready"
yield f"received: {value}"
values = numbered()
print("created generator")
print("first:", next(values))
print("second:", next(values))
try:
next(values)
except StopIteration as stop:
print("return value:", stop.value)
messages = receive_one()
print("primed:", next(messages))
print("sent:", messages.send("record"))
print("delegated:", list(wrapper()))
The file groups numbered(), receive_one(), and wrapper() with the calls that exercise them. I ran this named file so the printed transcript can be checked against its listing.
Step 2: Run the file and follow each pause
Save the listing as yield_demo.py. The command below runs that file, which I ran in the article workspace with exit code 0.
pankaj@askpython:~$ python3 yield_demo.py
created generator
generator body started
first: 10
resumed after 10
second: 20
return value: finished
primed: ready
sent: received: record
delegated: ['alpha', 'beta', 'subgenerator returned: part complete']
[exit 0]
I checked the screenshot against the captured output: it shows “return value: finished”, the priming message, and the delegated results.
In receive_one(), next() produces “ready”, then send(“record”) supplies the value for the assignment. A non-None send before priming raises TypeError. The “sent” output shows the generator’s response.
The wrapper delegates to part() with yield from. That expression accepts an iterable, forwards its produced items, and evaluates to the subgenerator’s return value when the delegated iterator finishes.
| Consumer | Choose it when |
|---|---|
| for loop | The caller should process values until the generator finishes |
| next() | The caller must request one item or handle StopIteration itself |
| send() | The caller must pass a value into a generator that is already suspended |
| yield from | One generator should delegate iteration and generator messages to another |
| list() | A finite result should be stored for indexing or another pass |
The consumer chooses between automatic iteration, explicit calls, generator input, and delegation.
For a CSV importer, keep parsing and validation in the generator while consuming code decides whether to store valid records or log invalid ones, leaving persistence policy outside the parser.
What edge cases should you know about generators?
A generator is an iterator with a lifetime: after it finishes, later requests cannot restart it. The rules below help distinguish normal exhaustion from a mistake in how values are requested.
| Case | What happens | What to do |
|---|---|---|
| Call next() after completion | Python raises StopIteration | Catch it only when using next() directly, or let a for loop handle it |
| Loop over the same generator again | No values remain after its first pass | Call the generator function again to create a new iterator |
| Send a non-None value before the first yield | The generator has not reached a suspension point | Prime it with next() or send(None) first |
| Return from a generator | Iteration ends and carries the return value in StopIteration | Use return for completion, not a manually raised StopIteration |
| Convert an unbounded generator to a list | Collection cannot finish because the source has no end | Stop after a chosen number of values or use a finite source |
StopIteration signals ordinary completion. An exception such as ValueError raised while producing a value propagates to the caller, which can handle that failure around the consuming operation.
How do completion values differ from yielded values?
The yielded value and the signal that iteration has ended have separate meanings.
| Situation | Result | What to do |
|---|---|---|
| Call next(iterator, default) after exhaustion | The call returns default instead of raising StopIteration | Use it for one optional request |
| Yield None | None is an ordinary item | Check iterator completion separately |
| Reach the end without a return value | The completion value is None | Inspect it through StopIteration.value only when needed |
| Raise StopIteration inside the generator body | Python converts an unhandled StopIteration to RuntimeError | Use return for normal completion |
The generator object itself is only part of the memory picture. Its source may already hold data, and its own local variables can keep references to earlier items.
When a generator owns an open file, use a with statement and ensure early-stopping consumers close the generator. Its finally block can release the resource when it finishes or when close() injects GeneratorExit. A generator should not yield another value while handling that close request.
Calling throw(error) injects an exception at the suspended yield expression. The generator can handle it or let it reach the caller, which makes throw() useful for signaling cancellation or a recoverable input problem.
yield from also delegates more than values when its source is another generator. It forwards send(), throw(), and close() behavior to that subgenerator, so the surrounding function can keep a clean boundary around the delegated work.
For a source with no natural end, itertools.islice(iterator, limit) bounds how many values you consume. Calling list() on an unbounded iterator waits forever because the source never signals completion.
Conclusion
When tracing a generator, write down each next() or send() call beside the statement it reaches. The reference links below describe the full protocol for delegation and completion.
For the wider iterator model, read the AskPython guide to generators. If you are comparing generator consumption with a normal list loop, see how to iterate over a Python list.
FAQ
These answers cover common questions about generator values, completion, and memory.
What is the difference between yield and return?
Yield produces a value and suspends the generator so it can resume later. Return ends the generator function, and its completion value is available through StopIteration.value.
What happens when a Python generator runs out of values?
The generator is exhausted. A for loop stops automatically, while a direct call to next() raises StopIteration.
Can a Python generator be used more than once?
A generator iterator is single pass. Call its generator function again to create a fresh iterator, or store the values in a collection if they must be revisited.
Do generators always use less memory than lists?
No. A generator can avoid building a separate result collection, but its source and current values still use memory. A caller can also collect all yielded values into a list.
Can a generator yield None as a value?
Yes. None is a normal yielded value. A generator ends when it raises StopIteration as part of iterator completion.

