Basic Behavior of a Generator Used as a Coroutine


In [16]:
def simple_coroutine():
    print('-> coroutine started')
    x = yield
    print('-> coroutine received:', x)

In [17]:
my_coro = simple_coroutine()
my_coro


Out[17]:
<generator object simple_coroutine at 0x000000AB4F9B0FC0>

In [18]:
next(my_coro)


-> coroutine started

In [19]:
my_coro.send(42)


-> coroutine received: 42
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-19-6eb1f5a64a1d> in <module>()
----> 1 my_coro.send(42)

StopIteration: 

In [20]:
my_coro = simple_coroutine()
my_coro.send(1729)


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-20-25f8d2897de2> in <module>()
      1 my_coro = simple_coroutine()
----> 2 my_coro.send(1729)

TypeError: can't send non-None value to a just-started generator

In [21]:
def simple_coro2(a):
    print('-> Started: a =', a)
    b = yield a
    print('-> Received: b =', b)
    c = yield a + b
    print('-> Received: c =', c)

In [22]:
my_coro2 = simple_coro2(14)

In [23]:
from inspect import getgeneratorstate

In [24]:
getgeneratorstate(my_coro2)


Out[24]:
'GEN_CREATED'

In [25]:
next(my_coro2)


-> Started: a = 14
Out[25]:
14

In [26]:
getgeneratorstate(my_coro2)


Out[26]:
'GEN_SUSPENDED'

In [27]:
my_coro2.send(28)


-> Received: b = 28
Out[27]:
42

In [28]:
my_coro2.send(99)


-> Received: c = 99
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-28-1aa524ca85a6> in <module>()
----> 1 my_coro2.send(99)

StopIteration: 

In [29]:
getgeneratorstate(my_coro2)


Out[29]:
'GEN_CLOSED'

Example: Coroutine to Compute a Running Average


In [30]:
def averager():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield average
        total += term
        count += 1
        average = total / count

In [31]:
coro_avg = averager()
next(coro_avg)
coro_avg.send(10)


Out[31]:
10.0

In [32]:
coro_avg.send(30)


Out[32]:
20.0

In [33]:
coro_avg.send(5)


Out[33]:
15.0

Decorators for Coroutine Priming


In [34]:
from functools import wraps

In [35]:
def coroutine(func):
    """Decorator: primes 'func' by advancing to first 'yield'"""
    @wraps(func)
    def primer(*args, **kwargs):
        gen = func(*args, **kwargs)
        next(gen)
        return gen
    return primer

In [36]:
@coroutine
def averager():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield average
        total += term
        count += 1
        average = total / count

In [37]:
coro_avg = averager()
getgeneratorstate(coro_avg)


Out[37]:
'GEN_SUSPENDED'

In [38]:
coro_avg.send(10)


Out[38]:
10.0

In [39]:
coro_avg.send(30)


Out[39]:
20.0

In [40]:
coro_avg.send(5)


Out[40]:
15.0

Coroutine Termination and Exception Handling


In [41]:
coro_avg = averager()
coro_avg.send(40)


Out[41]:
40.0

In [42]:
coro_avg.send(50)


Out[42]:
45.0

In [43]:
coro_avg.send('spam')


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-43-f4f0b1e9b4f3> in <module>()
----> 1 coro_avg.send('spam')

<ipython-input-36-11c6405b28a4> in averager()
      6     while True:
      7         term = yield average
----> 8         total += term
      9         count += 1
     10         average = total / count

TypeError: unsupported operand type(s) for +=: 'float' and 'str'

In [44]:
coro_avg.send(60)


---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-44-7fb61da68ae0> in <module>()
----> 1 coro_avg.send(60)

StopIteration: 

In [45]:
class DemoException(Exception):
    """An exception type for the demonstration"""

def demo_exc_handling():
    print('-> coroutine started')
    while True:
        try:
            x = yield
        except DemoException:
            print('*** DemoException handled. Continuing...')
        else:
            print('-> coroutine received: {!r}'.format(x))
    raise RuntimeError('This line should never run.')

In [46]:
exc_coro = demo_exc_handling()
next(exc_coro)


-> coroutine started

In [47]:
exc_coro.send(11)


-> coroutine received: 11

In [48]:
exc_coro.send(22)


-> coroutine received: 22

In [50]:
exc_coro.close()
getgeneratorstate(exc_coro)


Out[50]:
'GEN_CLOSED'

In [51]:
exc_coro = demo_exc_handling()
next(exc_coro)


-> coroutine started

In [52]:
exc_coro.send(11)


-> coroutine received: 11

In [53]:
exc_coro.throw(DemoException)


*** DemoException handled. Continuing...

In [54]:
getgeneratorstate(exc_coro)


Out[54]:
'GEN_SUSPENDED'

In [55]:
exc_coro = demo_exc_handling()
next(exc_coro)


-> coroutine started

In [56]:
exc_coro.send(11)


-> coroutine received: 11

In [57]:
exc_coro.throw(ZeroDivisionError)


---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-57-ab264ab92c46> in <module>()
----> 1 exc_coro.throw(ZeroDivisionError)

<ipython-input-45-d97194a698a2> in demo_exc_handling()
      6     while True:
      7         try:
----> 8             x = yield
      9         except DemoException:
     10             print('*** DemoException handled. Continuing...')

ZeroDivisionError: 

In [58]:
getgeneratorstate(exc_coro)


Out[58]:
'GEN_CLOSED'

In [59]:
class DemoException(Exception):
    """An exception type for the demonstration"""

def demo_finally():
    print('-> coroutine started')
    try:
        while True:
            try:
                x = yield
            except DemoException:
                print('*** DemoException handled. Continuing...')
            else:
                print('-> coroutine received: {!r}'.format(x))
    finally:
        print('-> coroutine ending')

Returning a Value from a Coroutine


In [66]:
from collections import namedtuple

Result_Averager = namedtuple('Result', 'count average')

def averager():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield
        if term is None:
            break
        total += term
        count += 1
        average = total / count
    return Result_Averager(count, average)

In [67]:
coro_avg = averager()
next(coro_avg)
coro_avg.send(10)
coro_avg.send(30)
coro_avg.send(6.5)
coro_avg.send(None)


---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-67-3cece4c6f1df> in <module>()
      4 coro_avg.send(30)
      5 coro_avg.send(6.5)
----> 6 coro_avg.send(None)

StopIteration: Result(count=3, average=15.5)

In [68]:
coro_avg = averager()
next(coro_avg)
coro_avg.send(10)
coro_avg.send(30)
coro_avg.send(6.5)
try:
    coro_avg.send(None)
except StopIteration as exc:
    result = exc.value

In [69]:
result


Out[69]:
Result(count=3, average=15.5)

Using yield from


In [70]:
def gen():
    for c in 'AB':
        yield c
    for i in range(1, 3):
        yield i

In [71]:
list(gen())


Out[71]:
['A', 'B', 1, 2]

In [73]:
def gen():
    yield from 'AB'
    yield from range(1, 3)

In [74]:
list(gen())


Out[74]:
['A', 'B', 1, 2]

In [75]:
def chain(*iterables):
    for it in iterables:
        yield from it

In [76]:
s = 'ABC'
t = tuple(range(3))
list(chain(s, t))


Out[76]:
['A', 'B', 'C', 0, 1, 2]

In [1]:
from collections import namedtuple

Result = namedtuple('Result', 'count average')

# the subgenerator
def averager():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield
        if term is None:
            break
        total += term
        count += 1
        average = total / count
    return Result(count, average)

# the delegating generator
def grouper(results, key):
    while True:
        results[key] = yield from averager()
        
# the client code, a.k.a. the caller
def main(data):
    results = {}
    for key, values in data.items():
        group = grouper(results, key)
        next(group)
        for value in values:
            group.send(value)
        group.send(None)   # important!
        
    # print(results) #uncomment to debug
    report(results)
    
def report(results):
    for key, result in sorted(results.items()):
        group, unit = key.split(';')
        print('{:2} {:5} averaging {:2f}{}'.format(
            result.count, group, result.average, unit))
        
data = {
    'girls; kg': [40.9, 38.5, 44.3, 42.2, 45.2, 41.7, 44.5, 38.0, 40.6, 44.5],
    'girls; m': [1.6, 1.51, 1.4, 1.3, 1.41, 1.39, 1.33, 1.46, 1.45, 1.43],
    'boys; kg': [39.0, 40.8, 43.2, 40.8, 43.1, 38.6, 41.4, 40.6, 36.3],
    'boys; m': [1.38, 1.5, 1.32, 1.25, 1.37, 1.48, 1.25, 1.49, 1.46],
}

The Meaning of yield from


In [8]:
from taxi_sim import taxi_process

In [9]:
taxi = taxi_process(ident=13, trips=2, start_time=0)

In [10]:
next(taxi)


Out[10]:
Event(time=0, proc=13, action='leave garage')

In [11]:
taxi.send(_.time + 7)


Out[11]:
Event(time=7, proc=13, action='pick up passenger')

In [12]:
taxi.send(_.time + 23)


Out[12]:
Event(time=30, proc=13, action='drop off passenger')

In [13]:
taxi.send(_.time + 5)


Out[13]:
Event(time=35, proc=13, action='pick up passenger')

In [14]:
taxi.send(_.time + 48)


Out[14]:
Event(time=83, proc=13, action='drop off passenger')

In [15]:
taxi.send(_.time + 1)


Out[15]:
Event(time=84, proc=13, action='going home')

In [16]:
taxi.send(_.time + 10)


---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-16-44c1369744b6> in <module>()
----> 1 taxi.send(_.time + 10)

StopIteration: 

In [ ]: