Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python asyncio, futures and yield from

Tags:

Consider the following program (running on CPython 3.4.0b1):

import math
import asyncio
from asyncio import coroutine

@coroutine
def fast_sqrt(x):
   future = asyncio.Future()
   if x >= 0:
      future.set_result(math.sqrt(x))
   else:
      future.set_exception(Exception("negative number"))
   return future


def slow_sqrt(x):
   yield from asyncio.sleep(1)
   future = asyncio.Future()
   if x >= 0:
      future.set_result(math.sqrt(x))
   else:
      future.set_exception(Exception("negative number"))
   return future


@coroutine
def run_test():
   for x in [2, -2]:
      for f in [fast_sqrt, slow_sqrt]:
         try:
            future = yield from f(x)
            print("\n{} {}".format(future, type(future)))
            res = future.result()
            print("{} result: {}".format(f, res))
         except Exception as e:
            print("{} exception: {}".format(f, e))


loop = asyncio.get_event_loop()
loop.run_until_complete(run_test())

I have 2 (related) questions:

  1. Even with the decorator on fast_sqrt, Python seems to optimize away the Future created in fast_sqrt altogether, and a plain float is returned. Which then blows up in run_test() in the yield from

  2. Why do I need to evaluate future.result() in run_test to retrieve the value of fire the exception? The docs say that yield from <future> "suspends the coroutine until the future is done, then returns the future’s result, or raises an exception". Why do I manually need to retieve the future's result?

Here is what I get:

oberstet@COREI7 ~/scm/tavendo/infrequent/scratchbox/python/asyncio (master)
$ python3 -V
Python 3.4.0b1
oberstet@COREI7 ~/scm/tavendo/infrequent/scratchbox/python/asyncio (master)
$ python3 test3.py

1.4142135623730951 <class 'float'>
<function fast_sqrt at 0x00B889C0> exception: 'float' object has no attribute 'result'

Future<result=1.4142135623730951> <class 'asyncio.futures.Future'>
<function slow_sqrt at 0x02AC8810> result: 1.4142135623730951
<function fast_sqrt at 0x00B889C0> exception: negative number

Future<exception=Exception('negative number',)> <class 'asyncio.futures.Future'>
<function slow_sqrt at 0x02AC8810> exception: negative number
oberstet@COREI7 ~/scm/tavendo/infrequent/scratchbox/python/asyncio (master)

Ok, I found the "issue". The yield from asyncio.sleep in slow_sqrt will make it a coroutine automatically. The waiting needs to be done differently:

def slow_sqrt(x):
   loop = asyncio.get_event_loop()
   future = asyncio.Future()
   def doit():
      if x >= 0:
         future.set_result(math.sqrt(x))
      else:
         future.set_exception(Exception("negative number"))
   loop.call_later(1, doit)
   return future

All 4 variants are here.