Subroutines are special cases of... coroutines. – Donald Knuth
Coroutines are a natural way of expressing many algorithms, such as simulations, games, asynchronous I/O, and other forms of event- driven programming or co-operative multitasking.
Each componenet is one of the building blocks of the one underneath:
Iterator objects represents a sequence of data. The iterator specification (protocol) includes 2 methods each iterator object must implement:
__iter__
- return the object itself.__next__
- returns the next object in the sequence, raises StopIteration
if empty.# A class implementing the iterator protocol
class iter_range(object):
def __init__(self, n):
self.i = 0
self.n = n
def __iter__(self):
return self
def __next__ (self):
if self.i < self.n:
i = self.i
self.i += 1
return i
else:
raise StopIteration()
it = iter_range(3)
for i in it:
print(i, end=' ')
0 1 2
Generators helps us generate iterators easily. A generator is essentially a functions that contains a yield
statement in it's body.
def gen_range(n):
i = 0
while i < n:
yield i
i += 1
it = gen_range(3)
for i in it:
print(i, end=' ')
0 1 2
Coroutines are very similar to generators but they utilize the yield statement to consume values.
def grep(pattern):
print('Looking for {}'.format(pattern))
while True:
line = yield
if pattern in line:
print(line)
g = grep('python')
next(g)
Looking for python
g.send('Yeah, but no, but yeah, but no')
g.send('A series of tubes')
g.send("python coroutines rock!")
python coroutines rock!
A coroutine decorater to remove the need for calling next
each time.
def coroutine(func):
def start(*args,**kwargs):
cr = func(*args,**kwargs)
next(cr)
return cr
return start
close
and throw
¶close
is called a coroutine, a GeneratorExit
exception is thrown, and the coroutine will end (the fact the exception is caught doesn't matter, the coroutine will exit anyhow).throw
, just that you can decide which exeption will be thrown and you can return a value (see examples).# close() example
@coroutine
def add_up():
s = 0
try:
while True:
x = yield
print(x, end=' ')
s += x
except GeneratorExit:
print('\nThe sum is {}. Cleaning up.'.format(s))
adder_gen = add_up()
for i in range(5):
adder_gen.send(i)
adder_gen.close()
0 1 2 3 4 The sum is 10. Cleaning up.
# throw() example
@coroutine
def throw_me_an_exception():
try:
while True:
x = yield
except InterruptedError as e:
print(e)
yield 'got it'
thrower_gen = throw_me_an_exception()
answer = thrower_gen.throw(InterruptedError, 'stop it')
print(answer)
#next(thrower_gen)
stop it got it
It seems that there is a great similarity between regular objects and coroutines: they both has data members that can hold states. Then why use coroutines? Let's see David Beasely's grep benchmark comparison:
# Benchmark authored by David Beasely - http://www.dabeaz.com/coroutines/index.html
class GrepHandler(object):
def __init__(self, pattern, target):
self.pattern = pattern
self.target = target
def send(self, line):
if self.pattern in line:
self.target.send(line)
@coroutine
def grep(pattern, target):
while True:
line = yield
if pattern in line:
target.send(line)
# A null-sink to send data
@coroutine
def null():
while True: item = yield
line = 'python is nice'
p1 = grep('python', null()) # Coroutine
p2 = GrepHandler('python', null()) # Object
%timeit -n 1000 p1.send(line)
%timeit -n 1000 p2.send(line)
1000 loops, best of 3: 423 ns per loop 1000 loops, best of 3: 465 ns per loop
So by using coroutines we get:
As PEP 342 mentions and Knuth & Ruskey decribes thouroughly in Deconstructing Coroutines, coroutines can supply an elegant solution to a varaity of problems, in fact Python's asycio package is based on them being called by an event loop (but that's for another talk).
# An illustration of coroutines 'branching' ability.
from IPython.display import Image,display_png
display_png(Image('images/branchy.png'))
# A sink. A coroutine that receives data
@coroutine
def printer():
while True:
line = yield
print(line)
# A filter.
@coroutine
def grep(pattern,target):
while True:
line = yield
if pattern in line:
target.send(line)
# Broadcast a stream onto multiple targets
@coroutine
def broadcast(*targets):
while True:
item = yield
for target in targets:
target.send(item)
def producer(broadcast):
with open('log') as f:
for line in f:
broadcaster.send(line)
if __name__ == '__main__':
p = printer()
producer(broadcast(grep('startup',p), grep('font',p), grep('libjs',p)))
2015-05-01 11:13:38 startup archives unpack 2015-05-01 11:13:45 install fonts-font-awesome:all <none> 4.2.0~dfsg-1 2015-05-01 11:13:45 status half-installed fonts-font-awesome:all 4.2.0~dfsg-1 2015-05-01 11:13:45 status triggers-pending fontconfig:amd64 2.11.1-0ubuntu6 2015-05-01 11:13:46 status unpacked fonts-font-awesome:all 4.2.0~dfsg-1 2015-05-01 11:13:46 status unpacked fonts-font-awesome:all 4.2.0~dfsg-1 2015-05-01 11:13:46 install fonts-mathjax:all <none> 2.5.1-1 2015-05-01 11:13:46 status half-installed fonts-mathjax:all 2.5.1-1 2015-05-01 11:13:46 status unpacked fonts-mathjax:all 2.5.1-1 2015-05-01 11:13:46 status unpacked fonts-mathjax:all 2.5.1-1 2015-05-01 11:13:47 install libjs-highlight.js:all <none> 8.2+ds-4 2015-05-01 11:13:47 status half-installed libjs-highlight.js:all 8.2+ds-4 2015-05-01 11:13:47 status unpacked libjs-highlight.js:all 8.2+ds-4 2015-05-01 11:13:47 status unpacked libjs-highlight.js:all 8.2+ds-4 2015-05-01 11:13:47 install libjs-highlight:all <none> 8.2+ds-4 2015-05-01 11:13:47 status half-installed libjs-highlight:all 8.2+ds-4 2015-05-01 11:13:48 status unpacked libjs-highlight:all 8.2+ds-4 2015-05-01 11:13:48 status unpacked libjs-highlight:all 8.2+ds-4 2015-05-01 11:13:48 install libjs-jquery:all <none> 1.7.2+dfsg-3ubuntu2 2015-05-01 11:13:48 status half-installed libjs-jquery:all 1.7.2+dfsg-3ubuntu2 2015-05-01 11:13:49 status unpacked libjs-jquery:all 1.7.2+dfsg-3ubuntu2 2015-05-01 11:13:49 status unpacked libjs-jquery:all 1.7.2+dfsg-3ubuntu2 2015-05-01 11:13:49 install libjs-jquery-ui:all <none> 1.10.1+dfsg-1 2015-05-01 11:13:49 status half-installed libjs-jquery-ui:all 1.10.1+dfsg-1 2015-05-01 11:13:52 status unpacked libjs-jquery-ui:all 1.10.1+dfsg-1 2015-05-01 11:13:53 status unpacked libjs-jquery-ui:all 1.10.1+dfsg-1 2015-05-01 11:13:53 install libjs-marked:all <none> 0.3.2+dfsg-1 2015-05-01 11:13:53 status half-installed libjs-marked:all 0.3.2+dfsg-1 2015-05-01 11:13:54 status unpacked libjs-marked:all 0.3.2+dfsg-1 2015-05-01 11:13:55 status unpacked libjs-marked:all 0.3.2+dfsg-1 2015-05-01 11:13:55 install libjs-mathjax:all <none> 2.5.1-1 2015-05-01 11:13:55 status half-installed libjs-mathjax:all 2.5.1-1 2015-05-01 11:13:59 status unpacked libjs-mathjax:all 2.5.1-1 2015-05-01 11:13:59 status unpacked libjs-mathjax:all 2.5.1-1 2015-05-01 11:13:59 install libjs-underscore:all <none> 1.7.0~dfsg-1ubuntu1 2015-05-01 11:13:59 status half-installed libjs-underscore:all 1.7.0~dfsg-1ubuntu1 2015-05-01 11:13:59 status unpacked libjs-underscore:all 1.7.0~dfsg-1ubuntu1 2015-05-01 11:13:59 status unpacked libjs-underscore:all 1.7.0~dfsg-1ubuntu1 2015-05-01 11:14:09 trigproc fontconfig:amd64 2.11.1-0ubuntu6 <none> 2015-05-01 11:14:09 status half-configured fontconfig:amd64 2.11.1-0ubuntu6 2015-05-01 11:14:09 status installed fontconfig:amd64 2.11.1-0ubuntu6 2015-05-01 11:14:14 startup packages configure 2015-05-01 11:14:14 configure fonts-font-awesome:all 4.2.0~dfsg-1 <none> 2015-05-01 11:14:14 status unpacked fonts-font-awesome:all 4.2.0~dfsg-1 2015-05-01 11:14:14 status half-configured fonts-font-awesome:all 4.2.0~dfsg-1 2015-05-01 11:14:14 status installed fonts-font-awesome:all 4.2.0~dfsg-1 2015-05-01 11:14:14 configure fonts-mathjax:all 2.5.1-1 <none> 2015-05-01 11:14:14 status unpacked fonts-mathjax:all 2.5.1-1 2015-05-01 11:14:14 status half-configured fonts-mathjax:all 2.5.1-1 2015-05-01 11:14:14 status installed fonts-mathjax:all 2.5.1-1 2015-05-01 11:14:14 configure libjs-highlight.js:all 8.2+ds-4 <none> 2015-05-01 11:14:14 status unpacked libjs-highlight.js:all 8.2+ds-4 2015-05-01 11:14:14 status half-configured libjs-highlight.js:all 8.2+ds-4 2015-05-01 11:14:14 status installed libjs-highlight.js:all 8.2+ds-4 2015-05-01 11:14:14 configure libjs-highlight:all 8.2+ds-4 <none> 2015-05-01 11:14:14 status unpacked libjs-highlight:all 8.2+ds-4 2015-05-01 11:14:14 status half-configured libjs-highlight:all 8.2+ds-4 2015-05-01 11:14:15 status installed libjs-highlight:all 8.2+ds-4 2015-05-01 11:14:15 configure libjs-jquery:all 1.7.2+dfsg-3ubuntu2 <none> 2015-05-01 11:14:15 status unpacked libjs-jquery:all 1.7.2+dfsg-3ubuntu2 2015-05-01 11:14:15 status half-configured libjs-jquery:all 1.7.2+dfsg-3ubuntu2 2015-05-01 11:14:15 status installed libjs-jquery:all 1.7.2+dfsg-3ubuntu2 2015-05-01 11:14:15 configure libjs-jquery-ui:all 1.10.1+dfsg-1 <none> 2015-05-01 11:14:15 status unpacked libjs-jquery-ui:all 1.10.1+dfsg-1 2015-05-01 11:14:15 status half-configured libjs-jquery-ui:all 1.10.1+dfsg-1 2015-05-01 11:14:15 status installed libjs-jquery-ui:all 1.10.1+dfsg-1 2015-05-01 11:14:15 configure libjs-marked:all 0.3.2+dfsg-1 <none> 2015-05-01 11:14:15 status unpacked libjs-marked:all 0.3.2+dfsg-1 2015-05-01 11:14:15 status half-configured libjs-marked:all 0.3.2+dfsg-1 2015-05-01 11:14:15 status installed libjs-marked:all 0.3.2+dfsg-1 2015-05-01 11:14:15 configure libjs-mathjax:all 2.5.1-1 <none> 2015-05-01 11:14:15 status unpacked libjs-mathjax:all 2.5.1-1 2015-05-01 11:14:16 status half-configured libjs-mathjax:all 2.5.1-1 2015-05-01 11:14:16 status installed libjs-mathjax:all 2.5.1-1 2015-05-01 11:14:16 configure libjs-underscore:all 1.7.0~dfsg-1ubuntu1 <none> 2015-05-01 11:14:16 status unpacked libjs-underscore:all 1.7.0~dfsg-1ubuntu1 2015-05-01 11:14:16 status half-configured libjs-underscore:all 1.7.0~dfsg-1ubuntu1 2015-05-01 11:14:16 status installed libjs-underscore:all 1.7.0~dfsg-1ubuntu1 2015-05-01 11:14:21 startup packages configure 2015-05-04 11:58:08 startup archives unpack 2015-05-04 11:58:30 startup packages configure 2015-05-04 11:58:33 startup packages configure 2015-05-04 13:30:49 startup archives unpack 2015-05-04 13:31:05 startup packages configure 2015-05-04 13:31:09 startup packages configure 2015-05-04 14:36:24 startup archives unpack 2015-05-04 14:36:52 startup packages configure 2015-05-04 14:37:06 startup packages configure 2015-05-04 14:37:07 startup archives install
In this example, we need fetch data from furniture.com. As with any pipe we can abstract the steps taken to 3: