A generator is a lazy iterator that produces values on demand instead of building a full collection in memory.
You create one with: 1. A generator function that uses yield, or 2. A generator expression: (x * x for x in range(10)).
Generators implement the iterator protocol (__iter__ / __next__). Each next() resumes the function until the next yield. When the function returns, iteration ends with StopIteration.
Why use them
- Stream large files or pipelines without loading everything.
- Compose transformations (map/filter style) cheaply.
- Express infinite or very large sequences safely.
def countdown(n):
while n > 0:
yield n
n -= 1
gen = countdown(3)
print(next(gen)) # 3
print(next(gen)) # 2
print(list(countdown(3))) # [3, 2, 1]
# Generator expression (lazy) vs list comprehension (eager)
lazy = (n * n for n in range(1_000_000))
eager = [n * n for n in range(1_000_000)]Key property: Generators are single-pass. Once exhausted, you must create a new one.