What does calling `gen.throw(ValueError)` do to a generator paused at a `yield`?
answer
- You can push more than values inward
- It lands where the frame is paused
- The body may catch it and continue
- Uncaught, it comes back out at you
- contextlib.contextmanager is built on it
basics
~20 sIt raises that exception inside the generator, at the exact yield where the frame is suspended, as if the yield itself had failed. If the body catches it and yields again, throw returns the new yielded value; otherwise the exception propagates to the caller and the generator closes.
solid answer
~50 s`throw` is the exception-shaped twin of `send`: instead of injecting a value as the result of the suspended `yield`, it injects an exception raised at that point. The generator's own `try`/`except` around the `yield` therefore sees it and can handle it, log it and loop back to another `yield` — in which case `throw` returns that next yielded value, exactly like `send` does. If nothing in the body catches it, the exception unwinds the generator frame, runs any `finally` blocks, propagates out of the `throw()` call to the caller, and leaves the generator closed for good. Throwing into a generator that has never been advanced raises the exception at the start of the body without running any of it. Pass an exception class or instance; the old three-argument `(type, value, traceback)` form is deprecated since Python 3.12.
code
python · 15 linesdef resilient():
while True:
try:
item = yield
except ValueError as exc:
print("skipped:", exc)
continue
print("processing:", item)
g = resilient()
next(g) # prime
g.send("frame-1") # processing: frame-1
g.throw(ValueError("corrupt frame")) # skipped: corrupt frame
g.send("frame-2") # processing: frame-2
g.close()go deeper
Recall that a generator object has throw alongside send and close, and that it delivers an exception into the paused generator rather than raising it where you stand.
Explain the three outcomes — caught and yielding again, caught and returning, or uncaught and propagating — and name the return value of throw in the first case.
Demonstrate that you know the stdlib depends on this: contextlib.contextmanager throws the with body's exception at the generator's yield, which is why the try must wrap the yield. Mention the PEP 479 StopIteration trap.
Weigh whether out-of-band signalling through throw is a defensible interface at all, or whether an explicit protocol — a sentinel item, a status object, a real state machine — is easier for a team to reason about and to test.
## Injecting an exception instead of a value A suspended generator is a live call frame parked mid-expression at a `yield`. `gen.send(v)` resumes that frame with `v` as the result of the `yield`. `gen.throw(exc)` resumes it by **raising** at the same point, as though the `yield` expression itself had blown up. Everything the language already knows about exceptions then applies inside the generator's own body: an enclosing `try`/`except` catches it, an enclosing `try`/`finally` runs its cleanup, and an unhandled exception unwinds the frame. That is the whole idea, and the rest is consequences. ## The three outcomes Once you call `gen.throw(SomeError("detail"))` on a suspended generator, exactly one of three things happens: 1. **The body catches it and reaches another `yield`.** `throw` returns that newly yielded value, just as `send` would. The generator is still alive and still suspended, one step further on. 2. **The body catches it and then returns (or falls off the end).** `throw` raises `StopIteration` in the caller; the generator is closed. 3. **Nothing catches it.** The exception propagates out of the `throw()` call into the calling code, `finally` blocks in the generator run on the way out, and the generator is left closed. Note where the traceback points: the frames of the generator body appear in it, because the exception really was raised there. Two edge states are worth knowing. Throwing into a generator that has never been advanced raises the exception at the top of the body — no statement of it runs, and the generator ends up closed. Throwing into an already-exhausted generator simply propagates the exception back to you, since there is no frame left to receive it. ## The stdlib runs on this The clearest real use is `contextlib.contextmanager`. When you write a context manager as a generator with a single `yield`, and the `with` body raises, the wrapper delivers that exception *into* the generator with `throw` at its `yield` point. That is precisely why writing ```python @contextlib.contextmanager def guard(): try: yield resource except ValueError: ... finally: release(resource) ``` behaves the way intuition expects: the `try` wraps the `yield`, so it wraps the `with` body. If the generator swallows the thrown exception and returns normally, the `with` statement suppresses it; if it re-raises, the exception continues to propagate. All of that is `throw` semantics, not special context-manager magic. ## What you would use it for directly * **Signalling a consumer.** A long-lived generator that accumulates work items can be told out-of-band that a batch is bad, by throwing a domain exception in at its `yield` and letting it decide whether to skip the item or tear down. * **Testing cleanup paths.** `throw` is the cheapest way to prove that a generator's `except` and `finally` blocks behave when the consumer fails halfway through — no need to construct the real failure. * **Understanding the async model.** The same protocol underpins how an event loop delivers a cancellation into a suspended coroutine. ## Traps **Do not throw `StopIteration` in to stop a generator.** Since PEP 479 (Python 3.7) a `StopIteration` that escapes a generator body is converted into `RuntimeError: generator raised StopIteration`, precisely so that an accidental `StopIteration` cannot silently truncate iteration. To end a generator, use `close()`. **`throw` is not a caller-side raise.** Candidates often say it raises the exception "in the caller". It raises it in the *generator*; the caller only sees it if the generator declines to handle it. **Signature.** Pass a class or an instance: `gen.throw(ValueError)` or `gen.throw(ValueError("bad frame"))`. The legacy three-argument `(type, value, traceback)` spelling still works but emits a `DeprecationWarning` from Python 3.12 onward — new code should pass a single instance, which carries its own traceback anyway. **A generator that yields on every exception never dies.** If the body wraps its `yield` in a bare `except:` that loops forever, a caller can no longer stop it with an ordinary exception, and even `close()` will fail with `RuntimeError` because the generator ignores `GeneratorExit`. Catch narrowly, and let control exceptions through.
- What does `gen.throw()` return when the generator catches the exception and yields again?The newly yielded value, exactly as `send` would return it. The generator stays alive and suspended one step further on, which is what makes `throw` usable as an out-of-band signal to a running consumer rather than only as a way to kill it.
- Why can't you throw `StopIteration` into a generator to end it cleanly?Since Python 3.7 (PEP 479) a `StopIteration` escaping a generator body is turned into `RuntimeError: generator raised StopIteration`, so the trick both fails and produces a confusing error. Use `close()`, which raises `GeneratorExit` and is designed for exactly this.
- Is `gen.throw(ValueError, ValueError('x'), None)` still valid on Python 3.14?It still runs, but it emits a `DeprecationWarning` — the three-argument signature has been deprecated since 3.12. Pass a single class or instance instead; an instance already carries its own traceback, so nothing is lost.
- What happens if you throw into a generator that has not been primed?The exception is raised at the very start of the body, so no statement of the generator ever executes and no `except` or `finally` inside it can run. The exception propagates straight back to the caller and the generator is left closed.
saying these in an interview costs you the question
- Says throw() raises the exception in the caller's frame
- Thinks the generator always dies after a throw
- Uses throw(StopIteration) to end a generator
- Believes throw needs the exception already raised somewhere
- Assumes the three-argument throw signature is current
- Confuses throw() with re-raising after next() failed