The Yield Keyword in SimPy: Why It Matters
If you've looked at any SimPy code, you've seen yield everywhere. It's not decoration—it's the mechanism that makes simulation work.
What Is Yield?
yield turns a function into a generator. Instead of running to completion, the function pauses at each yield and can resume later.
def counter():
yield 1
yield 2
yield 3
gen = counter()
print(next(gen)) # 1
print(next(gen)) # 2
print(next(gen)) # 3
Each next() runs until the next yield, returns that value, then pauses.
Why SimPy Uses Generators
SimPy needs processes that: - Start, pause, and resume - Hand control back to the simulator - Remember their state between pauses
Generators do exactly this. Naturally. Elegantly.
def process(env):
print("Step 1")
yield env.timeout(5) # Pause here
print("Step 2")
yield env.timeout(5) # Pause here
print("Step 3")
SimPy runs this generator, advancing through each yield. The function maintains its state—local variables, position—between yields.
Yield vs Return
return ends a function. yield pauses it.
def using_return():
return 1
return 2 # Never reached
return 3 # Never reached
def using_yield():
yield 1
yield 2 # Reached on second call
yield 3 # Reached on third call
In SimPy, processes use yield to pause and return to end:
def process(env):
yield env.timeout(5)
yield env.timeout(5)
return "Completed" # Process ends, returns value
What Happens at Yield
When a SimPy process yields:
- Control returns to SimPy - The process pauses
- Event is registered - SimPy schedules when to resume
- Time can advance - Other events may fire
- Process resumes - When the event occurs
This is cooperative multitasking. Processes voluntarily yield control.
Yield vs Yield From
Python has both:
# yield - return a single value
yield env.timeout(5)
# yield from - delegate to another generator
yield from subprocess(env)
In SimPy, both work but yield from is useful for sub-processes:
def step1(env):
yield env.timeout(3)
print("Step 1 done")
def step2(env):
yield env.timeout(2)
print("Step 2 done")
def main_process(env):
yield from step1(env)
yield from step2(env)
print("All done")
yield from runs the sub-generator completely before continuing.
Receiving Values
Yield can receive values:
def process(env):
result = yield env.process(other_process(env))
print(f"Got: {result}")
The yielded event's value is returned when resuming. This is how you get return values from sub-processes.
Common Mistakes
Forgetting Yield
def broken_process(env):
env.timeout(5) # Creates timeout but doesn't wait!
print("This prints immediately")
Without yield, the timeout is created but ignored. The process doesn't pause.
Yielding Non-Events
def broken(env):
yield 5 # Error! Must yield events
You must yield SimPy events (timeouts, requests, processes), not arbitrary values.
Using Return Too Early
def process(env):
return env.timeout(5) # Returns generator, doesn't wait
This returns immediately with a timeout object, rather than waiting.
Generator State
The beauty of generators is they maintain state:
def process(env):
counter = 0
while True:
yield env.timeout(1)
counter += 1
print(f"Counter: {counter}")
counter persists between yields. No globals needed. No class state. Just local variables.
Debugging Generators
Print statements help:
def process(env):
print(f"Starting at {env.now}")
yield env.timeout(5)
print(f"After first timeout at {env.now}")
yield env.timeout(5)
print(f"After second timeout at {env.now}")
You'll see exactly when execution pauses and resumes.
Performance
Generators are efficient. Suspending and resuming is cheap—much cheaper than threads. A SimPy simulation can have thousands of active processes without performance issues.
The Mental Model
Think of yield as "wait here for this event":
def customer(env, counter):
yield counter.request() # Wait for counter
yield env.timeout(5) # Wait 5 time units
# Continue after both waits
Reading yield as "wait for" makes SimPy code intuitive.
Advanced: Generator Send
SimPy uses generator .send() to pass values back:
def demo():
received = yield 1
print(f"Got: {received}")
received = yield 2
print(f"Got: {received}")
gen = demo()
print(next(gen)) # 1
print(gen.send("A")) # Prints "Got: A", then 2
gen.send("B") # Prints "Got: B"
SimPy uses this to return event values when resuming processes. You rarely need to know this—it just works.
Summary
The yield keyword: - Turns functions into generators - Pauses execution until an event occurs - Maintains state between pauses - Is the foundation of SimPy's process model
Understand yield. Understand generators. SimPy becomes obvious.
Next Steps
Discover the Power of Simulation
Want to become a go-to expert in simulation with Python? The Complete Simulation Bootcamp will show you how simulation can transform your career and your projects.
Explore the Bootcamp