Python Generator send vs next: What's the Difference?
Understand the difference between Python generator send() and next(): how send() passes values into a paused generator, and when to use each for iteration, coroutines, and state machines.
Python generators can be resumed from a suspension point with either the built-in next() function or the generator's send() method. Both make the generator run until the next yield, but they differ in what the caller passes into the generator: next() passes nothing, so the yield expression evaluates to None; send(value) passes value, which becomes that expression's result. This distinction enables two-way communication with a running generator.
What Is the Difference Between send() and next()?
In Python, a generator object can be advanced in two ways: the built-in next() function and the generator object's send() method. Both resume the generator from its current suspension point, but they differ in what they pass into the generator. next() advances the generator without passing a value, while send(value) passes a value that becomes the result of the yield expression at the suspension point.
Consider this minimal generator:
def simple_generator(): received = yield 'ready' print(f'Received: {received}') yield 'done'
Using next():
gen = simple_generator() print(next(gen)) # 'ready' print(next(gen)) # Received: None, then 'done'
Using send():
gen = simple_generator() print(gen.send(None)) # 'ready' - must send None first print(gen.send('hello')) # Received: hello, then 'done'
The key difference is that send(value) passes value into the generator, and that value becomes the result of the yield expression. Calling next(gen) is equivalent to gen.send(None), but if you use send() to start a generator, send(None) is the only allowed first call. Sending any other value before the generator reaches its first yield raises TypeError.
How next() Resumes a Generator
When you call next(gen), the generator runs until it hits a yield statement, then suspends, returning the yielded value. On the next call, it resumes right after that yield, executes the rest of the code until the next yield or until it returns. If the generator returns without yielding, StopIteration is raised.
Calling next(gen) is the standard way to advance a generator manually, and for loops use the same iterator protocol. The important limitation is that next() does not inject any data into the generator; the yield expression evaluates to None when resumed this way.
How send() Passes a Value Into a Generator
The send() method also resumes the generator, but it takes an argument that becomes the value of the yield expression at the point where the generator was suspended. This lets the caller communicate with the generator, creating a two-way channel.
Important: You cannot call send() with a non-None value on a generator that hasn't started yet. The first call to send() must be send(None), because there is no yield expression to receive a value before the generator starts. After the first call, you can use send(value) to pass data.
The value passed to send() is the result of the yield expression. For example:
def accumulator(): total = 0 while True: value = yield total if value is None: continue total += value
Here, yield total returns the current total, but also accepts a value that becomes value when execution resumes. You can use send() to add numbers to the accumulator:
acc = accumulator() print(acc.send(None)) # 0 print(acc.send(10)) # 10 print(acc.send(5)) # 15
Each send() call resumes the generator, assigns the sent value to value, updates total, and then yields the new total.
The yield Expression: Both Output and Input
The core concept is that yield is not just a statement that produces a value; it is an expression that can also receive a value. When the generator is suspended at a yield, the expression's value is whatever is passed via send(), or None if resumed via next().
This dual nature makes generators useful for generator-based coroutines and cooperative multitasking. The generator can both produce data and consume data from the caller, enabling patterns like data pipelines, state machines, and event loops.
Practical Use Cases for send()
The most common use case for send() is implementing generator-based coroutines. A coroutine is a function that can suspend and resume, maintaining state between calls. With send(), you can pass data into the coroutine at each resumption, allowing it to process a stream of inputs.
For example, a logging coroutine that receives log messages and writes them to a file:
def log_writer(filename): with open(filename, 'w') as f: while True: message = yield f.write(message + '\n') logger = log_writer('log.txt') logger.send(None) # start the coroutine logger.send('error: something failed') logger.send('info: operation completed') logger.close()
Another use case is a state machine where the sent value, combined with the current state, determines the next transition. This pattern is useful for parsing, protocol handling, and event processing.
Common Mistakes When Using send()
One common mistake is calling send(value) on a generator that hasn't been started. This raises TypeError: can't send non-None value to a just-started generator. You must always call send(None) first.
Another mistake is mixing next() and send() without understanding the state. When you call next(gen), the yield expression evaluates to None. If the generator expects a non-None value, it may behave differently. For example, in the accumulator above, calling next(acc) instead of send(10) makes value become None, so the generator continues without updating the total. That might be intentional, but it is often a bug.
Also, forgetting to handle StopIteration when the generator finishes can lead to runtime errors. send() raises StopIteration just like next() when the generator exits.
Performance and Maintainability Considerations
Performance is rarely the reason to choose between next() and send(), because both rely on the same generator machinery. The more important cost is readability: a value sent into a generator can change its control flow in ways that are not visible from the yielded values alone, so send() usually makes code harder to follow.
When deciding between next() and send(), consider whether the generator needs to receive input. If you only need to iterate over a sequence, next() is simpler and clearer. If you need two-way communication, send() is the appropriate tool. Avoid using send() for simple iteration, as it adds unnecessary complexity.
Advanced Pattern: A Simple State Machine with send()
A practical demonstration of send() is building a state machine. Consider a traffic light controller that transitions between states based on an external timeout value:
def traffic_light(): state = 'red' while True: timeout = yield state if timeout is None: timeout = 1 if state == 'red': state = 'green' elif state == 'green': state = 'yellow' else: state = 'red'
You can drive it with send():
light = traffic_light() print(light.send(None)) # 'red' print(light.send(3)) # 'green' print(light.send(2)) # 'yellow' print(light.send(1)) # 'red'
This pattern is useful for simulations, protocol handling, and any system where the next state depends on both the current state and an external input.