Python Throw Generator: How generator.throw() Works
python throw generator: Learn how generator.throw() injects exceptions at a generator's suspension point, how to catch them inside the generator, and when to use this...
What generator.throw() Does
The python throw generator pattern refers to generator.throw(), a method that injects an exception into a generator at its current suspension point. When you call throw() on a paused generator, the exception is raised at the exact yield expression where the generator is waiting, as if that expression had raised the exception itself. The generator resumes execution with that exception, and its behavior depends on how the code around the yield handles it.
This is different from send(), which resumes the generator with a value. throw() resumes it with an error condition. Both operate on the same suspension point, but they communicate different intent to the generator.
How the Exception Reaches the Generator
The method accepts an exception class, an exception instance, or a class plus value and traceback. In modern Python, passing an instance is the most common form.
def reader(): while True: line = yield print(f"Read: {line}") gen = reader() next(gen) # start the generator gen.throw(ConnectionError("socket closed"))
Here, ConnectionError is raised at the yield inside reader(). Because there is no try around that yield, the exception propagates out of the generator and back to the caller. The generator is then closed. The caller sees the same exception object it passed in.
Catching the Injected Exception Inside the Generator
To make throw() useful, the generator should catch the exception at the suspension point. This turns the generator into something that can be told to change behavior while it is paused.
def counter(): value = 0 while True: try: value = yield value except ValueError: value = 0 gen = counter() print(next(gen)) # 0 print(gen.send(5)) # 5 print(gen.throw(ValueError("reset"))) # 0
When throw() is called, the ValueError is raised at yield value. The except clause resets the internal state, the loop continues, and the generator yields 0 again. The caller receives that value as the return of throw(). This is the core cooperative pattern: send() feeds data in, throw() feeds an error condition in, and the generator decides how to respond.
If the generator catches the exception but then returns instead of yielding again, throw() raises StopIteration in the caller. If the generator raises a different exception while handling the injected one, that new exception propagates to the caller.
Comparing throw(), send(), and close()
| Method | What it resumes with | Generator state after |
|---|---|---|
send(value) | A normal value | Suspended at the next yield |
throw(exc) | An exception | Depends on how the generator handles it |
close() | GeneratorExit | Closed |
send() and throw() are symmetric: one delivers data, the other delivers an error. Both return the next yielded value when the generator continues. close() is a special case that raises GeneratorExit and is meant to trigger cleanup in finally blocks, not to be caught and resumed.
Practical Use Cases for throw()
The most common use is cooperative cancellation. A long-running generator can check for an injected exception and unwind cleanly.
class StopPolling(Exception): pass def poll(): try: while True: yield read_sensor() except StopPolling: print("shutting down") gen = poll() next(gen) gen.throw(StopPolling())
The generator runs its except cleanup, then stops. The caller does not need a separate flag variable or a sentinel value flowing through the data channel.
Another use is testing error paths. A generator that normally produces values can be forced into an error branch with throw() without restructuring the whole implementation. This is especially useful when the generator wraps I/O or network calls that are hard to simulate.
Edge Cases and Common Pitfalls
Calling throw() on a generator that has already finished or been closed raises StopIteration. There is no suspension point left to inject into, so the exception has nowhere to go.
A common mistake is assuming the injected exception is the only thing the caller can receive. If the generator catches the exception and yields a value, throw() returns that value. If the generator instead raises a different exception, that exception replaces the one you passed in. Code that calls throw() should handle both outcomes.
Another pitfall is using throw() where a plain return value would be clearer. If the generator only needs to know whether to continue, an explicit send(False) or a sentinel value keeps the control flow visible. Exception injection crosses a boundary that is easy to miss when reading the code later.
Runtime Behavior and Maintainability
throw() does not copy the exception or add meaningful overhead beyond normal exception handling. The cost is the same as raising an exception inside any function. The real cost is cognitive: the caller and the generator share an implicit contract about which exceptions mean what. If the generator changes its handling, callers can break in ways that are not visible at the call site.
Keep the contract narrow. Define a small set of exception types for generator control, document what each one means, and avoid reusing generic exceptions like ValueError for control flow. When the generator is part of a public API, prefer explicit state signals over exception injection unless the generator genuinely needs to unwind work in progress.
For most generator code, send() with a value is easier to reason about than throw(). Reserve throw() for cancellation, cleanup, and test scenarios where an exception is the most honest way to express what happened.