12. Coroutines and its importance in async operations

  • The origins of the yield from keyword

    In [6] , Chapter 16 Page 480, Luciano Ramalho writes “How Coroutines evolved form Generators” from that Lecture we learn that:

    ..In addition to .send(..), PEP 340 also added throw(..) and .close() methods that allowed a caller to send a datum into the generator, thrown an exception to be handled inside the generator, and terminate it.

    PEP 380 , makes two syntax changes to generators functions to make them more useful coroutines:

    • “yield from” syntax enables complex generators to be refactored into smaller nested generators .

    • A generator can now return a value. (previously attempts to return a value would raise a SyntaxError exception) See “return Result ( count , average )” in the example below

    We can go deeper on the details of PEP 380, created by Gregory Ewing reading [16]

    • Coroutines are just special generators …. You send values into

    • Coroutines are very similar to generators in Python: they both use yield.

    • Just the meaning of yield implies to ‘let things go’, to stop and let others continue. So, the yield keyword

      has an asynchronous meaning. Brett [8] .

    • Coroutines suspend execution in the yield statement and pass control to the caller

      along with any value to the right of yield.

    • Normal coroutines in python use yield to wait to receive an elements from the caller. Usually yield is to the right

    • The caller send data to the coroutine using send.

    • Coroutines are functions whose execution you can pause.

    We could see some examples of use of coroutines now , but what will be really useful is to see David Beazley examples in “A Curious Course on Coroutines” [9] …, Part 1 : Introduction to Generators and Coroutines

    We’ll see in this talk how we start separating coroutines from generators in its use and purpose, and how the former “evolved” to create async/await syntax to be used in async I/O operation in Python

Then looking at Chapter [2] on David Bealey [9] , and the code in “copipe.py” we will see how how to hook up a pipeline with coroutines.

Note

Note that stacking coroutines , meaning having one coroutine doing something passing the result of its operation to another coroutine so it does another things, with that kind of structure , we could get pretty powerful functionality. “yield from” will allow us to successfully concatenate coroutines and get messages passed back and forth from the caller to any coroutine in the pipe.

Now is time we can understand and see “yield from” in Action , by taken a look to [6] Example 16-17, “Fluent Python”, Luciano Ramalho Page [496]

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 )

    # output report
    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 ] , }

 if __name__ == ' __main__ ':
    main(data)

We can easily see now the power of “yield from” and how this keywords will allow us to extract values and send values directly to the subgenerator, which yield data back at the caller.