๐Generators: yield, yield from, send()LESSON~15 min
Generators: yield, yield from, send()
Generators are one of Python's most elegant features. They let you write iterators with ordinary function syntax, produce values lazily (on demand), and compose powerful data pipelines โ all while using dramatically less memory than building full lists.
Generator Functions and yield
A generator function looks like a regular function but uses yield instead of return. When called, it doesn't execute immediately โ it returns a generator object (which is an iterator):
Execution is suspended at each yield. The function's local state (variables, position in the code) is preserved between calls to next().
Generator Objects Are Iterators
Generator objects implement both __iter__ and __next__, so they work everywhere iterators work:
Lazy Evaluation โ Values on Demand
The key advantage: a generator only computes the next value when you ask for it. This is called lazy evaluation:
Memory Comparison: list vs generator
For large sequences, generators use O(1) memory while lists use O(n):
Generator Expressions
Just as list comprehensions have a concise syntax, generators do too โ use parentheses instead of brackets:
Generator State: Lifecycle
A generator moves through distinct states:
yield from โ Delegating to Sub-generators
yield from iterable delegates to another iterable, yielding all its values:
yield from also properly handles send() and throw() calls by forwarding them to the sub-generator.
send() โ Passing Values Into a Generator
Generators are not just one-way pipes. You can send values back in with gen.send(value). The yield expression receives the sent value:
Important: You must call next(gen) (or gen.send(None)) first to advance to the first yield before you can send() a non-None value.
Practical Uses
Infinite sequences without memory cost:
Data pipelines โ chain transformations lazily:
Knowledge Check
What does a generator function return when called?
When is the body of a generator function first executed?