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.

Generator send() allows you to send values into a running generator, enabling two-way communication. This makes generators into simple coroutines.

Basic send() Usage

message
basic.py
Replay: real traced execution (multi-file project)
# 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 = "hello"
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 = "hi"
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 = "ready"
gen.send(message)
gen.send(42)
gen.send([1, 2, 3])

  1. gen ← ⟨generator object echo A⟩

    11# Create and prime generator12gen→ ⟨generator object echo A⟩ = echo()13next(gen⟨generator object echo A⟩)  # prime it (run until first yield)
  2. message ← hello, value ← hello

    6    while True:7        value→ hello = yield  # receive value8        print(f"Echo: {valuehello}")91011# Create and prime generator12gen = echo()13next(gen⟨generator object echo A⟩)  # prime it (run until first yield)1415# Send values16message→ hello = "hello"17#@message="hi", "ready"18gen⟨generator object echo A⟩.send(messagehello)19gen.send(42)
    outputEcho: hello
  3. value ← 42

    6    while True:7        value→ 42 = yield  # receive value8        print(f"Echo: {value42}")91011# Create and prime generator12gen = echo()13next(gen)  # prime it (run until first yield)1415# Send values16message = "hello"17#@message="hi", "ready"18gen⟨generator object echo A⟩.send(messagehello)19gen⟨generator object echo A⟩.send(42)20gen.send([1, 2, 3])
    outputEcho: 42
  4. value ← [1, 2, 3]

    6    while True:7        value→ [1, 2, 3] = yield  # receive value8        print(f"Echo: {value[1, 2, 3]}")91011# Create and prime generator12gen = echo()13next(gen)  # prime it (run until first yield)1415# Send values16message = "hello"17#@message="hi", "ready"18gen.send(message)19gen⟨generator object echo A⟩.send(42)20gen⟨generator object echo A⟩.send([1, 2, 3])
    outputEcho: [1, 2, 3]
  5. gen.send([1, 2, 3])

    19gen.send(42)20gen⟨generator object echo A⟩.send([1, 2, 3])
  1. gen ← ⟨generator object echo A⟩

    11# Create and prime generator12gen→ ⟨generator object echo A⟩ = echo()13next(gen⟨generator object echo A⟩)  # prime it (run until first yield)
  2. message ← hi, value ← hi

    6    while True:7        value→ hi = yield  # receive value8        print(f"Echo: {valuehi}")91011# Create and prime generator12gen = echo()13next(gen⟨generator object echo A⟩)  # prime it (run until first yield)1415# Send values16message→ hi = "hi"17gen⟨generator object echo A⟩.send(messagehi)18gen.send(42)
    outputEcho: hi
  3. value ← 42

    6    while True:7        value→ 42 = yield  # receive value8        print(f"Echo: {value42}")91011# Create and prime generator12gen = echo()13next(gen)  # prime it (run until first yield)1415# Send values16message = "hi"17gen⟨generator object echo A⟩.send(messagehi)18gen⟨generator object echo A⟩.send(42)19gen.send([1, 2, 3])
    outputEcho: 42
  4. value ← [1, 2, 3]

    6    while True:7        value→ [1, 2, 3] = yield  # receive value8        print(f"Echo: {value[1, 2, 3]}")91011# Create and prime generator12gen = echo()13next(gen)  # prime it (run until first yield)1415# Send values16message = "hi"17gen.send(message)18gen⟨generator object echo A⟩.send(42)19gen⟨generator object echo A⟩.send([1, 2, 3])
    outputEcho: [1, 2, 3]
  5. gen.send([1, 2, 3])

    18gen.send(42)19gen⟨generator object echo A⟩.send([1, 2, 3])
  1. gen ← ⟨generator object echo A⟩

    11# Create and prime generator12gen→ ⟨generator object echo A⟩ = echo()13next(gen⟨generator object echo A⟩)  # prime it (run until first yield)
  2. message ← ready, value ← ready

    6    while True:7        value→ ready = yield  # receive value8        print(f"Echo: {valueready}")91011# Create and prime generator12gen = echo()13next(gen⟨generator object echo A⟩)  # prime it (run until first yield)1415# Send values16message→ ready = "ready"17gen⟨generator object echo A⟩.send(messageready)18gen.send(42)
    outputEcho: ready
  3. value ← 42

    6    while True:7        value→ 42 = yield  # receive value8        print(f"Echo: {value42}")91011# Create and prime generator12gen = echo()13next(gen)  # prime it (run until first yield)1415# Send values16message = "ready"17gen⟨generator object echo A⟩.send(messageready)18gen⟨generator object echo A⟩.send(42)19gen.send([1, 2, 3])
    outputEcho: 42
  4. value ← [1, 2, 3]

    6    while True:7        value→ [1, 2, 3] = yield  # receive value8        print(f"Echo: {value[1, 2, 3]}")91011# Create and prime generator12gen = echo()13next(gen)  # prime it (run until first yield)1415# Send values16message = "ready"17gen.send(message)18gen⟨generator object echo A⟩.send(42)19gen⟨generator object echo A⟩.send([1, 2, 3])
    outputEcho: [1, 2, 3]
  5. gen.send([1, 2, 3])

    18gen.send(42)19gen⟨generator object echo A⟩.send([1, 2, 3])
send() - a method that resumes a generator and passes a value to the yield expression

Priming Generators

priming.py
Replay: real traced execution (multi-file project)
# 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)

  1. gen ← ⟨generator object receiver A⟩

    11# Without priming (error)12gen→ ⟨generator object receiver A⟩ = receiver()13try:
  2. try:

    12gen = receiver()13try:14    gen⟨generator object receiver A⟩.send(10)  # ERROR: can't send to a just-started generator15except TypeError as e:
  3. except TypeError as e:

    14    gen.send(10)  # ERROR: can't send to a just-started generator15except TypeError as e:16    print(f"Error: {ecan't send non-None value to a just-started generator}")
    outputError: can't send non-None value to a just-started generator
  4. gen ← ⟨generator object receiver B⟩

    18# With priming19gen→ ⟨generator object receiver B⟩ = receiver()20next(gen⟨generator object receiver B⟩)  # prime it - runs until first yield21gen.send(10)
  5. def receiver():

    4def receiver():5    print("Generator started")6    while True:
    outputGenerator started
  6. value ← 10

    6    while True:7        value→ 10 = yield8        print(f"Received: {value10}")91011# Without priming (error)12gen = receiver()13try:14    gen.send(10)  # ERROR: can't send to a just-started generator15except TypeError as e:16    print(f"Error: {e}")1718# With priming19gen = receiver()20next(gen⟨generator object receiver B⟩)  # prime it - runs until first yield21gen⟨generator object receiver B⟩.send(10)22gen.send(20)
    outputReceived: 10
  7. value ← 20

    6    while True:7        value→ 20 = yield8        print(f"Received: {value20}")91011# Without priming (error)12gen = receiver()13try:14    gen.send(10)  # ERROR: can't send to a just-started generator15except TypeError as e:16    print(f"Error: {e}")1718# With priming19gen = receiver()20next(gen)  # prime it - runs until first yield21gen⟨generator object receiver B⟩.send(10)22gen⟨generator object receiver B⟩.send(20)
    outputReceived: 20
  8. gen.send(20)

    21gen.send(10)22gen⟨generator object receiver B⟩.send(20)
priming - calling next() once to advance the generator to its first yield before using send()

Bidirectional Communication

bidirectional.py
Replay: real traced execution (multi-file project)
# 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))

  1. gen ← ⟨generator object running_average A⟩

    14# Use bidirectional generator15gen→ ⟨generator object running_average A⟩ = running_average()16print("Initial:", next(gen⟨generator object running_average A⟩))  # prime and get initial value
  2. total ← 0, count ← 0

    4def running_average():5    """Calculate running average of sent values"""6    total→ 0 = 07    count→ 0 = 08    while True:
  3. while True:

    pass 1 of 4
    7count = 08while True:9    value = yield total0 / count0 if count > 0 else 010    total += value
    All 4 passes — pass 1 is the card above
    passtotalcount
    100
    2101
    3302
    4603
  4. value ← 10, total ← 10, count ← 1

    8    while True:9        value→ 10 = yield total0 / count0 if count > 0 else 010        total→ 10 += value1011        count→ 1 += 1121314# Use bidirectional generator15gen = running_average()16print("Initial:", next(gen⟨generator object running_average A⟩))  # prime and get initial value1718print("After 10:", gen⟨generator object running_average A⟩.send(10))19print("After 20:", gen.send(20))
    outputInitial: 0
    Initial: 0
  5. value ← 20, total ← 30, count ← 2

    8    while True:9        value→ 20 = yield total10 / count1 if count > 0 else 010        total→ 30 += value2011        count→ 2 += 1121314# Use bidirectional generator15gen = running_average()16print("Initial:", next(gen))  # prime and get initial value1718print("After 10:", gen⟨generator object running_average A⟩.send(10))19print("After 20:", gen⟨generator object running_average A⟩.send(20))20print("After 30:", gen.send(30))
    outputAfter 10: 10.0
    After 10: 10.0
  6. value ← 30, total ← 60, count ← 3

    8    while True:9        value→ 30 = yield total30 / count2 if count > 0 else 010        total→ 60 += value3011        count→ 3 += 1121314# Use bidirectional generator15gen = running_average()16print("Initial:", next(gen))  # prime and get initial value1718print("After 10:", gen.send(10))19print("After 20:", gen⟨generator object running_average A⟩.send(20))20print("After 30:", gen⟨generator object running_average A⟩.send(30))
    outputAfter 20: 15.0
    After 20: 15.0
  7. print("After 30:", gen.send(30))

    19print("After 20:", gen.send(20))20print("After 30:", gen⟨generator object running_average A⟩.send(30))
    outputAfter 30: 20.0
yield expression - `value = yield result` both sends a result out and receives the next sent value

State Machines

state_machine.py
Replay: real traced execution (multi-file project)
# 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"))

  1. light ← ⟨generator object traffic_light A⟩

    20# Use state machine21light→ ⟨generator object traffic_light A⟩ = traffic_light()22print("Initial:", next(light⟨generator object traffic_light A⟩))
  2. state ← red

    4def traffic_light():5    """Traffic light state machine"""6    state→ red = "red"7    while True:
  3. while True:

    pass 1 of 5
    6state = "red"7while True:8    command = yield statered9    if command == "next":
    All 5 passes — pass 1 is the card above
    passstate
    1red
    2green
    3yellow
    4red
    5red
  4. command ← next

    7    while True:8        command→ next = yield statered9        if command == "next":10            if state == "red":11                state = "green"12            elif state == "green":13                state = "yellow"14            elif state == "yellow":15                state = "red"16        elif command == "reset":17            state = "red"181920# Use state machine21light = traffic_light()22print("Initial:", next(light⟨generator object traffic_light A⟩))2324print("Next:", light⟨generator object traffic_light A⟩.send("next"))25print("Next:", light.send("next"))
    outputInitial: red
    Initial: red
  5. if command == "next":

    pass 1 of 3
    8command = yield state9if commandnext == "next":10    if state == "red":11        state = "green"
    All 3 passes — pass 1 is the card above
    passstate
    1red green
    2green yellow
    3yellow red
  6. state ← green

    9if command == "next":10    if statered == "red":11        state→ green = "green"12    elif state == "green":
  7. command ← next

    7    while True:8        command→ next = yield stategreen9        if command == "next":10            if state == "red":11                state = "green"12            elif state == "green":13                state = "yellow"14            elif state == "yellow":15                state = "red"16        elif command == "reset":17            state = "red"181920# Use state machine21light = traffic_light()22print("Initial:", next(light))2324print("Next:", light⟨generator object traffic_light A⟩.send("next"))25print("Next:", light⟨generator object traffic_light A⟩.send("next"))26print("Next:", light.send("next"))
    outputNext: green
    Next: green
  8. state ← yellow

    11    state = "green"12elif stategreen == "green":13    state→ yellow = "yellow"14elif state == "yellow":
  9. command ← next

    7    while True:8        command→ next = yield stateyellow9        if command == "next":10            if state == "red":11                state = "green"12            elif state == "green":13                state = "yellow"14            elif state == "yellow":15                state = "red"16        elif command == "reset":17            state = "red"181920# Use state machine21light = traffic_light()22print("Initial:", next(light))2324print("Next:", light.send("next"))25print("Next:", light⟨generator object traffic_light A⟩.send("next"))26print("Next:", light⟨generator object traffic_light A⟩.send("next"))27print("Reset:", light.send("reset"))
    outputNext: yellow
    Next: yellow
  10. state ← red

    13        state = "yellow"14    elif stateyellow == "yellow":15        state→ red = "red"16elif command == "reset":
  11. command ← reset

    7    while True:8        command→ reset = yield statered9        if command == "next":10            if state == "red":11                state = "green"12            elif state == "green":13                state = "yellow"14            elif state == "yellow":15                state = "red"16        elif command == "reset":17            state = "red"181920# Use state machine21light = traffic_light()22print("Initial:", next(light))2324print("Next:", light.send("next"))25print("Next:", light.send("next"))26print("Next:", light⟨generator object traffic_light A⟩.send("next"))27print("Reset:", light⟨generator object traffic_light A⟩.send("reset"))
    outputNext: red
    Next: red
  12. state ← red

    15        state = "red"16elif commandreset == "reset":17    state→ red = "red"
  13. print("Reset:", light.send("reset"))

    26print("Next:", light.send("next"))27print("Reset:", light⟨generator object traffic_light A⟩.send("reset"))
    outputReset: red

close() and throw()

close_throw.py
Replay: real traced execution (multi-file project)
# 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")

  1. gen ← ⟨generator object logger A⟩

    14# Use close()15gen→ ⟨generator object logger A⟩ = logger()16next(gen⟨generator object logger A⟩)17gen.send("Starting")
  2. msg ← Starting

    7        while True:8            msg→ Starting = yield9            print(f"[LOG] {msgStarting}")10    except GeneratorExit:11        print("[LOG] Shutting down")121314# Use close()15gen = logger()16next(gen⟨generator object logger A⟩)17gen⟨generator object logger A⟩.send("Starting")18gen.send("Processing")
    output[LOG] Starting
  3. msg ← Processing

    7        while True:8            msg→ Processing = yield9            print(f"[LOG] {msgProcessing}")10    except GeneratorExit:11        print("[LOG] Shutting down")121314# Use close()15gen = logger()16next(gen)17gen⟨generator object logger A⟩.send("Starting")18gen⟨generator object logger A⟩.send("Processing")19gen.close()  # triggers GeneratorExit
    output[LOG] Processing
  4. gen.send("Processing")

    17gen.send("Starting")18gen⟨generator object logger A⟩.send("Processing")19gen⟨generator object logger A⟩.close()  # triggers GeneratorExit20print("Generator closed")
  5. except GeneratorExit:

    9        print(f"[LOG] {msg}")10except GeneratorExit:11    print("[LOG] Shutting down")
    output[LOG] Shutting down
    [LOG] Shutting down
  6. gen2 ← ⟨generator object logger B⟩

    18gen.send("Processing")19gen⟨generator object logger A⟩.close()  # triggers GeneratorExit20print("Generator closed")2122# Use throw()23gen2→ ⟨generator object logger B⟩ = logger()24next(gen2⟨generator object logger B⟩)25gen2.send("Message 1")
    outputGenerator closed
  7. msg ← Message 1

    7        while True:8            msg→ Message 1 = yield9            print(f"[LOG] {msgMessage 1}")10    except GeneratorExit:11        print("[LOG] Shutting down")121314# Use close()15gen = logger()16next(gen)17gen.send("Starting")18gen.send("Processing")19gen.close()  # triggers GeneratorExit20print("Generator closed")2122# Use throw()23gen2 = logger()24next(gen2⟨generator object logger B⟩)25gen2⟨generator object logger B⟩.send("Message 1")26try:
    output[LOG] Message 1
  8. gen2.send("Message 1")

    24next(gen2)25gen2⟨generator object logger B⟩.send("Message 1")26try:
  9. try:

    25gen2.send("Message 1")26try:27    gen2⟨generator object logger B⟩.throw(ValueError<class 'ValueError'>, "Simulated error")28except ValueError:
  10. except ValueError:

    27    gen2.throw(ValueError, "Simulated error")28except ValueError:29    print("Caught exception from generator")
    outputCaught exception from generator
close() and throw() - methods to gracefully shut down a generator or inject exceptions

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

Exercise: practical.py

Build a running average calculator using generator send()