Generator send() for Two-Way Communication
Overview
The `.send()` method allows passing data back IN to a generator. Instead of just yielding values out, `yield` becomes an expression that evaluates to the value sent in. Think of generators as mini-programs paused at `yield`. Calling `.send()` resumes them and hands them a value. This two-way communication is the foundation of coroutines and asynchronous programming in Python. Generators provide three methods for two-way communication: - `.send(value)`: Resumes execution and passes `value` into the `yield` expression. Returns the next yielded value, or raises `StopIteration` if the generator exits. - `.throw(type)`: Raises an exception of the given `type` at the paused `yield` expression. Returns the next yielded value, or raises `StopIteration` if it catches the exception and exits. - `.close()`: Raises a `GeneratorExit` at the paused `yield`. Returns `None`, and forces the generator to exit. You must prime a generator before sending a value. You cannot send a non-`None` value to a newly started generator, because it has not yet reached a `yield` expression to receive it. Doing so raises a `TypeError`. def greeter(): name = yield print(f"Hello {name}") g = greeter() g.send("Alice") # TypeError: can't send non-None value to a just-started generator This edge case is covered fully in `coroutines`.
It transforms generators from simple data producers (one-way) into stateful coroutines that can receive data, process it, and respond (two-way).
Where used: `asyncio` event loops, custom state machines, data streaming pipelines
Why learn this
- Understanding how `async`/`await` actually works under the hood
- Building memory-efficient data pipelines that react to input
- Creating custom coroutines that maintain internal state
Code walkthrough
def greeter():
print('Starting')
while True:
name = yield
print(f'Hello {name}')
g = greeter()
next(g)
g.send('Alice')
g.send('Bob')
Focus: name = yield
Aha moment
def echo():
received = yield 'Started'
yield f'You said: {received}'
e = echo()
print(next(e))
print(e.send('Python'))
Prediction: What does the second print output?
Common guess: You said: Started
The `yield` evaluates to the value sent IN (`'Python'`), which is assigned to `received`, and then the generator yields the formatted string back OUT.
Common mistakes
- Sending to an unprimed generator: You cannot call `.send()` with a non-`None` value on a generator that hasn't started yet. You must first prime it with `next(gen)` or `gen.send(None)` to advance it to the first `yield`.
- Ignoring the yielded value: When you call `.send()`, the generator runs until the NEXT `yield` and returns that value. Beginners often discard this return value, missing half the communication.
Glossary
- coroutines
- Special functions that can pause and resume their execution, allowing multiple tasks to run cooperatively. Example: `name = yield` inside a generator makes it a coroutine that can receive values via `.send()`.
- prime
- To prepare a generator or coroutine for its first use by advancing it to its initial pause point. Example: `next(gen)` or `gen.send(None)` primes the generator before you call `.send(value)`.
Recall questions
- What happens if you try to `.send()` a non-`None` value to a newly created generator before calling `next()`?
- What is the difference between a normal generator and a coroutine using `.send()`?
- What do the `.throw()` and `.close()` methods do on a generator?
Understanding checks
Why does calling `.send(5)` on a newly created generator raise a `TypeError`?
Because the generator hasn't reached a `yield` statement yet to receive the value.
A generator must be "primed" by advancing it to the first `yield` expression (usually by calling `next(gen)` or `gen.send(None)`) before it can receive data via `.send()`.
What is the output of `print(g.send('B'))`?
`'B'`
The first `next(g)` runs until the first `yield` and outputs `'A'`. Then `g.send('B')` resumes execution, assigning `'B'` to `val`, and immediately yields `val`.
Practice tasks
Running Average Generator
Complete the `running_average()` generator so that it receives numbers via `.send()` and yields the current average of all numbers received so far. The generator must yield `0.0` upon initialization.
Challenge
Stateful Filter Coroutine
Write a generator `threshold_filter(limit)` that receives numbers. If the received number is greater than `limit`, it yields the number. Otherwise, it yields `None`. Remember to handle the first `yield` which is needed just to prime the generator.