Coroutines in Python with examples

python_coroutines_consume.jpg

Generator functions that define consume and produce values are coroutines. The basic premise is that coroutines allow two functions to communicate with each other while running.

The difference between regular generators and coroutines is that coroutines don't simply return values to the calling function but can receive values as well.

Coroutines consume values using a (yield) statement as follows:

value = (yield)

With this syntax, execution pauses at this statement until the object's send method is invoked with an argument

coroutine.send(data)

Then, execution resumes, with value being assigned to the value of data.

def repeater():
    while True:
        received = yield
        print('Echo:', received)

rp = repeater()

next(rp) # Start the coroutine
rp.send('Hello')
rp.send('World')

#Echo: First
#Echo: Second

Since generators are lazy, you can't just send a value to a brand new generator. Before a value can be sent to the generator, either a result must be fetched using next() or a send(None) has to be issued so that the code is actually reached.

For example, say you want to implement a generator coroutine that yields the minimum value it’s been sent so far. Here, the bare yield prepares the coroutine with the initial minimum value sent in from the outside. Then the generator repeatedly yields the new minimum in exchange for the next value to consider.

def minimize():
    current = yield
    while True:
        value = yield current
        current = min(value, current)

The code consuming the generator can run one step at a time and will output the minimum value seen after each input.

it = minimize()
next(it) # Start the generator
print(it.send(10))
print(it.send(4))
print(it.send(22))
print(it.send(-1))

# 10
# 4
# 4
#-1

The generator function will seemingly run forever, making forward progress with each new call to send. Like threads, coroutines are independent functions that can consume inputs from their environment and produce resulting outputs. The difference is that coroutines pause at each yield expression in the generator function and resume after each call to send from the outside. This is the magical mechanism of coroutines.

comments powered by Disqus