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]
The terminal shows yielded values, a sent message, a completion value, and delegated results.

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.

Share.
Leave A Reply