A generator produces values one at a time, only when asked, holding nothing ahead of time.
Core idea
- Lazy evaluation: work is delayed until a value is needed; eager does it all up front (a list).
- A generator hands you one value, pauses, and does more work only when asked for the next.
Making one
- Generator function: uses
yieldinstead ofreturn; anyyieldin the body makes it a generator. - Calling it does not run the body; it returns a generator object.
- Generator expression: inline, round parens instead of square brackets:
(n*n for n in range(5)). - Drop the extra parens when it is a function’s only arg:
sum(n*n for n in range(5)).
Pause and resume
- At
yieldthe function freezes: locals keep their values, execution resumes on the next line next time. next(gen)pulls exactly one value; aforloop calls it automatically.- Exhaustion raises
StopIteration; aforloop catches this signal for you.
loop --next()--> gen: yield value, freeze
loop --next()--> gen: resume, yield, freeze
loop --next()--> gen: StopIteration (done)
Cost
- List of n values: O(n) space (all held at once).
- Generator: O(1) space (only current position + locals), any number of values.
- Time is O(n) either way; only the memory footprint shrinks.
Infinite generators
while True: yield ...never ends on its own; the caller stops withbreak.- The stop condition lives in the consumer, not the generator.
When to use
- Large or infinite/unknown-length sequences processed one item at a time.
- You might stop early and skip unneeded work.
- Prefer a list when data is small, reused (a generator is single-use, exhausted after one pass), or needs random access /
len().
Pitfalls
- Consumed once: after
list()or aforloop, re-iterating yields nothing; recreate it. (...)is lazy,[...]builds a full list; easy typo, very different memory.len(gen)andgen[0]raiseTypeError; convert withlist(gen)(brings back O(n)).print(gen)shows the object, not values; uselist()or a loop.- Infinite generator with no
breakhangs:list(count_from(0))never finishes.