Sometimes generators need input while running - like a parser that needs the next chunk of data, or a state machine that receives commands. The send() method enables two-way communication, turning generators into simple coroutines.

close() and throw()

basic.py
# Basic send()


def echo():
    """Generator that echoes received values"""
    while True:
        value = yield  # receive value
        print(f"Echo: {value}")


# Create and prime generator
gen = echo()
next(gen)  # prime it (run until first yield)

# Send values
message = 
gen.send(message)
gen.send(42)
gen.send([1, 2, 3])

# Basic send()


def echo():
    """Generator that echoes received values"""
    while True:
        value = yield  # receive value
        print(f"Echo: {value}")


# Create and prime generator
gen = echo()
next(gen)  # prime it (run until first yield)

# Send values
message = 
gen.send(message)
gen.send(42)
gen.send([1, 2, 3])

# Basic send()


def echo():
    """Generator that echoes received values"""
    while True:
        value = yield  # receive value
        print(f"Echo: {value}")


# Create and prime generator
gen = echo()
next(gen)  # prime it (run until first yield)

# Send values
message = 
gen.send(message)
gen.send(42)
gen.send([1, 2, 3])

priming.py
# Priming a generator


def receiver():
    print("Generator started")
    while True:
        value = yield
        print(f"Received: {value}")


# Without priming (error)
gen = receiver()
try:
    gen.send(10)  # ERROR: can't send to a just-started generator
except TypeError as e:
    print(f"Error: {e}")

# With priming
gen = receiver()
next(gen)  # prime it - runs until first yield
gen.send(10)
gen.send(20)

bidirectional.py
# Send and receive


def running_average():
    """Calculate running average of sent values"""
    total = 0
    count = 0
    while True:
        value = yield total / count if count > 0 else 0
        total += value
        count += 1


# Use bidirectional generator
gen = running_average()
print("Initial:", next(gen))  # prime and get initial value

print("After 10:", gen.send(10))
print("After 20:", gen.send(20))
print("After 30:", gen.send(30))

state_machine.py
# Stateful coroutine


def traffic_light():
    """Traffic light state machine"""
    state = "red"
    while True:
        command = yield state
        if command == "next":
            if state == "red":
                state = "green"
            elif state == "green":
                state = "yellow"
            elif state == "yellow":
                state = "red"
        elif command == "reset":
            state = "red"


# Use state machine
light = traffic_light()
print("Initial:", next(light))

print("Next:", light.send("next"))
print("Next:", light.send("next"))
print("Next:", light.send("next"))
print("Reset:", light.send("reset"))

close_throw.py
# Closing and throwing


def logger():
    """Generator that logs messages"""
    try:
        while True:
            msg = yield
            print(f"[LOG] {msg}")
    except GeneratorExit:
        print("[LOG] Shutting down")


# Use close()
gen = logger()
next(gen)
gen.send("Starting")
gen.send("Processing")
gen.close()  # triggers GeneratorExit
print("Generator closed")

# Use throw()
gen2 = logger()
next(gen2)
gen2.send("Message 1")
try:
    gen2.throw(ValueError, "Simulated error")
except ValueError:
    print("Caught exception from generator")

How It Works

  1. Call next(gen) to start the generator (prime it)
  2. Generator runs until yield
  3. Call gen.send(value) to resume and pass a value
  4. yield expression evaluates to the sent value
  5. Generator continues until next yield

Use Cases

  • Coroutines: functions that can pause and receive input
  • Pipelines: processing data with feedback
  • State machines: receive commands, send responses
send() - a method that resumes a generator and passes a value to the yield expression
priming - calling next() once to advance the generator to its first yield before using send()
yield expression - `value = yield result` both sends a result out and receives the next sent value
close() and throw() - methods to gracefully shut down a generator or inject exceptions

Exercise: practical.py

Build a running average calculator using generator send()