What does `gen.send(value)` do to a generator paused at a `yield`?
answer
- The keyword works in both directions
- yield can sit right of an equals sign
- The caller pushes a value back in
- next() is the same as send(None)
- A fresh generator has no waiting yield
basics
~20 ssend(value) resumes the generator and makes value the result of the yield expression it was paused on. The generator runs to its next yield, and send returns that yielded value, or raises StopIteration if the generator finishes.
solid answer
~40 sIn Python `yield` is an expression, not only a statement, so a generator body can write `received = yield item`. When the generator is suspended it is parked mid-expression at that `yield`; `gen.send(value)` makes `value` the result of that expression and resumes the frame with all its locals intact. Execution continues to the next `yield`, whose value `send` returns to the caller — the common mistake is expecting `send` to hand back the value you passed in. If the body returns instead, `send` raises `StopIteration`. `next(gen)` is exactly `gen.send(None)`. A brand-new generator is parked at the top of the body, not at a `yield`, so there is no expression waiting to receive anything: `gen.send(5)` on it raises `TypeError: can't send non-None value to a just-started generator`. Advance it once with `next(gen)` or `gen.send(None)` first.
code
python · 14 linesdef running_average():
total = 0.0
count = 0
average = None
while True:
value = yield average
total += value
count += 1
average = total / count
avg = running_average()
next(avg) # prime: run the body up to the first yield
print(avg.send(10)) # 10.0
print(avg.send(20)) # 15.0go deeper
Recall that a generator can receive as well as produce: value = yield item stores whatever the caller sent. Be ready to say that next(gen) and gen.send(None) are the same call.
Explain the mechanics: the injected value becomes the result of the suspended yield expression, send returns the next yielded value, and an unprimed generator raises TypeError because no yield is waiting to receive anything.
Show the failure modes you have actually hit — the missing priming call, StopIteration from a generator that already returned, and ValueError: generator already executing when two callers share one generator object.
Own the design call: a send-driven generator is a stateful, single-consumer object, so decide deliberately when that suspended frame is a clean model for a stage and when an explicit class or an async coroutine is the more maintainable shape for the team.
## `yield` runs in two directions A generator function is any `def` whose body contains `yield`; calling it runs none of that body and returns a generator object instead. Most code only ever pulls from that object — `for chunk in gen`, `next(gen)`, `list(gen)`. But since PEP 342 (Python 2.5) `yield` is an **expression**, which means the generator body can also receive something at the point where it suspended: ```python received = yield produced ``` `produced` goes out to whoever resumed the generator; `received` is whatever that caller pushes back in on the next resume. `gen.send(value)` is the push. ## What actually happens on a `send` A suspended generator owns a live frame: its local variables, its instruction pointer parked mid-expression at a `yield`, and its exception state. `send(value)` does three things in order. It makes `value` the result of the suspended `yield` expression. It resumes the frame. Then it waits for the frame to leave control again, which can happen three ways: * the body reaches another `yield` — `send` returns that newly yielded value; * the body executes `return`, or falls off the end — `send` raises `StopIteration`; * an exception escapes the body — it propagates out of the `send` call, and the generator is left closed. So `send` is symmetric with `next`: both resume, both return the next yielded value, both raise `StopIteration` at the end. The only difference is the value injected at the suspension point. `next(gen)` is defined to behave exactly like `gen.send(None)`, and both are legal on a generator whose `yield` is written as a bare statement — the injected `None` is simply discarded. The single most common misreading is that `send(x)` returns `x`. It does not. The value goes *in*; what comes *out* is the next thing the generator yields. ## Priming A generator that has never been advanced is parked at the very top of its body, before the first statement. There is no `yield` expression sitting there waiting for a value, so CPython refuses to invent one: ``` TypeError: can't send non-None value to a just-started generator ``` The fix is **priming**: call `next(gen)` (or the identical `gen.send(None)`) once, which runs the body forward to the first `yield` and returns whatever it produced. Only after that does a meaningful `send` work. Because forgetting this is easy, real code that builds send-driven generators usually wraps the construction so the priming call cannot be skipped. ## Other states `send` can hit * **Exhausted generator.** Once the body has returned, every later `send` — with any argument, including `None` — raises `StopIteration` again. A generator never restarts; there is no rewind. * **Currently running generator.** Resuming a generator from inside itself, or from a second thread while it is mid-step, raises `ValueError: generator already executing`. A generator object supports exactly one active caller at a time; it is a suspended call frame, not a shared queue. * **Generator expressions.** `(x*2 for x in data)` produces a generator object with a full `send`/`throw`/`close` surface, but its implicit `yield` is a statement with nowhere to store an incoming value, so `gen.send("anything")` simply behaves like `next(gen)` and drops the argument. ## Syntax detail `value = yield item` needs no parentheses, because the `yield` expression is the whole right-hand side. Anywhere it is a *sub*-expression, parentheses are required: `total += (yield total)`, `print((yield))`. This is a grammar rule, not a style preference — omitting them is a `SyntaxError`. ## Why it matters `send` is what turns a generator from a lazy sequence into a two-way conversation with a resumable computation: a running accumulator you feed values into, a state machine parked between events, a consumer that reports back on each item it swallows. It is also the machinery `await` was built on top of — the modern `async def` syntax hides the same suspend/resume/inject protocol behind dedicated keywords. Understanding `send` is therefore the cheapest way to understand what suspension actually costs and what a resumed frame really is.
- What is the difference between `next(gen)` and `gen.send(None)`?None at all. `next(gen)` is defined as resuming with `None` injected at the suspension point, which is exactly what `gen.send(None)` does; both return the next yielded value and both raise `StopIteration` when the body finishes. `send(None)` is legal on a never-advanced generator precisely because the injected value is `None`.
- What does `gen.send(value)` raise once the generator has finished?`StopIteration`, every time and for any argument. A generator that has returned is closed permanently — its frame is gone, so there is nothing to resume. If you need to iterate again you call the generator function a second time to build a fresh generator object.
- Can you send a value into a generator expression such as `(x for x in data)`?You get a real generator object with a `send` method, but its implicit `yield` is a statement with no left-hand side, so the value has nowhere to land and is discarded — `send(x)` behaves just like `next()`. Two-way communication requires a generator function whose body writes `value = yield ...`.
- What happens if a generator is resumed while it is already running?`ValueError: generator already executing`. A generator object wraps one suspended call frame, and that frame can have only one active caller — reentering it from inside itself, or racing two threads onto the same generator, is rejected outright rather than corrupting the frame.
saying these in an interview costs you the question
- Says send() returns the value you passed in
- Calls send with a real value on a brand-new generator
- Believes yield is always a statement with no value
- Thinks send restarts the generator from the top
- Assumes send appends to a queue the generator reads
- Claims next() and send(None) behave differently