Explain Codes LogoExplain Codes Logo

What is the purpose of the "send" function on Python generators?

python
coroutines
asynchronous-programming
generators
Anton ShumikhinbyAnton Shumikhin·Mar 12, 2025
TLDR

You can use send() to inject values into a generator at yield points, allowing you to customize its behavior during its life cycle. This is unlike next(), which simply continues to the next yield.

Here's an example:

def countdown(start): current = start while current > 0: received = (yield current) current = received if received is not None else current - 1 gen = countdown(5) print(next(gen)) # Outputs 5. Press start to begin. print(gen.send(3)) # Outputs 3. We got a secret cheat code here! print(next(gen)) # Outputs 2. Let the regular countdown resume.

From the example, gen.send(3) resets the countdown to 3, demonstrating the dynamic control made possible with send().

Using send to simplify asynchronous code

When working with asynchronous operations, particularly in frameworks like Twisted, the send function can make your code easier to read and less complex. You can use @defer.inlineCallbacks decorator and send to manage Deferred values, allowing you to avoid callback chaos. As shown in the following example, send streamlines your asynchronous code by enabling coroutines to generate and receive results on-the-fly.

@defer.inlineCallbacks def async_operation(): result = yield some_deferred_operation() print('The operation has now been completed:', result) new_result = yield another_deferred_operation(result) defer.returnValue(new_result)

The yield statements within @defer.inlineCallbacks automatically handle Deferreds, enabling linear and sequential programming, which is more readable, with asynchronous code.

send: A Two-Way Street

The send() function opens a two-way communication channel with your generators. This bidirectional flow allows your generators to become coroutines, which are optimized for asynchronous I/O tasks.

def user_input_processor(): result = "" while True: user_input = (yield result) result = f"Processed: {user_input}" processor = user_input_processor() next(processor) # Initialize the processor print(processor.send("Hello")) # Outputs: "Processed: Hello" print(processor.send("World!")) # Outputs: "Processed: World!"

Note how send changes the behavior of the generator in real-time based on external input, making your generators reactive and adaptive.

Dive into Real-time Behavior Change

Using the send() method, you can alter the behavior of a generator in real-time by feeding it new values. This way, you render your generators flexible and responsive to position updates executed by the calling code, as shown in the below code:

def traffic_light(): color = 'Green' while True: color = (yield color) color = 'Red' if color == 'Yellow' else 'Green' light = traffic_light() print(next(light)) # Outputs 'Green' print(light.send('Yellow')) # Outputs 'Red'. Oops! Prepare to stop.

Sustained State with "send"

Generators with send can hold their state in-between yields and throughout their lifespan. This trait allows you to implement usage patterns such as running totals or state machines without having to resort to bulkier methodologies:

def running_total(): total = 0 while True: addend = (yield total) total += addend if addend is not None else 0 accumulator = running_total() next(accumulator) # Warming up the accumulator print(accumulator.send(5)) # Adds 5; Outputs 5 print(accumulator.send(10)) # Adds 10; Outputs 15

Here, send() adds values to the running total of the accumulator.

Interactive Patterns with “send”

Combining the use of send() and yield enables interactive generator patterns. These generators are more than just passive producers of sequences. They are interactive machines that can respond dynamically to external instructions:

def interactive_quiz(): score = 0 question = (yield f"Score: {score}. Let's roll: What is 2+2?") while True: if question == "4": score += 1 question = (yield f"Wow! Score: {score}. Next question: What is the capital of France?") else: question = (yield f"Hmmm, that's incorrect. Score: {score}. Give another shot!") quizinator = interactive_quiz() next(quizinator) # Fires the first question print(quizinator.send("4")) # Fires the second question

This generator functions like a self-contained, simple interactive quiz.