Python Generator send vs next: What's the Difference?
python generator send vs next: Understand the difference between send() and next() in Python generators, with practical examples and advanced patterns for two-way comm...
python generator send vs next requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
What Is the Difference Between send() and next()?
In Python, a generator object has two primary methods for advancing its execution: next() and send(). 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 any value, while send() can pass 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. When you call next(gen), it is equivalent to gen.send(None), but send(None) is required to start a generator that hasn't been advanced yet.
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.
The next() method is the standard way to iterate over a generator. It's what for loops use internally. The key point is that next() does not inject any data into the generator; the yield expression evaluates to None when resumed via next().
How send() Passes a Value Into a Generator
The send() method is an extension of next(). It 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 allows the caller to communicate with the generator, effectively 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 in the next iteration. 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 is what makes generators useful for 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 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 determines the next state 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 break. For example, in the accumulator above, if you call next(acc) instead of send(10), value becomes None, and the generator continues without updating the total. That might be intentional, but it's 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
From a performance standpoint, send() is not significantly slower than next(). The overhead is minimal because both are implemented in the generator machinery. The real cost is in the complexity of the code. Using send() makes the generator's control flow less obvious, because the value passed in can change behavior in non-linear ways. This can make the code harder to read and maintain, especially for developers unfamiliar with coroutine patterns.
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 a timer:
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.