Python generators — yield and send explained
yield keyword, generator function, generator object, send method, yield from, generator vs iterator, practical generator patterns
Generators
A generator function uses yield instead of return. Calling it returns a generator object. The body executes lazily — only advancing when next() is called.
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
gen = fibonacci()
for _ in range(8):
print(next(gen), end=" ") # 0 1 1 2 3 5 8 13
def squares(n):
for i in range(n):
yield i ** 2
print(list(squares(5))) # [0, 1, 4, 9, 16]
yield from
def chain(*iterables):
for it in iterables:
yield from it # delegates to sub-iterator
print(list(chain([1, 2], [3, 4], [5])))
# [1, 2, 3, 4, 5]
send()
def accumulator():
total = 0
while True:
value = yield total
total += value
acc = accumulator()
next(acc) # prime the generator
print(acc.send(10)) # 10
print(acc.send(5)) # 15
Generators are the foundation of async Python. Understanding yield makes async/await conceptually straightforward.
Generators are one of Python's most elegant features. They let you write pipeline-style data processing that is both readable and memory-efficient. A generator pipeline — read → filter → transform → aggregate — processes one item at a time through all stages, never materialising the full intermediate dataset. This scales to arbitrarily large files. The yield from syntax simplifies delegation to sub-iterators and is also the foundation of coroutines. Python's async/await syntax is built on the same suspension mechanism as generators, so mastering generators gives you the conceptual foundation for asynchronous programming.
