Python的asyncio,期货和收益率

时间:2013-12-22 11:53:27

标签: python future yield coroutine python-asyncio

考虑以下程序(在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())

我有两个(相关的)问题:

  1. 即使在fast_sqrt上有装饰器,Python似乎也会完全放弃在fast_sqrt中创建的Future,并返回一个普通的float。然后会在run_test()

  2. 中的yield from中爆发
  3. 为什么我需要评估future.result()中的run_test来检索触发异常的值? docs表示yield from <future>“暂停协程直到将来完成,然后返回未来的结果,或者引发异常”。为什么我手动需要记录未来的结果?

  4. 这是我得到的:

    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)
    

    好的,我找到了“问题”。 yield from asyncio.sleep中的slow_sqrt会自动将其设为协程。等待需要以不同的方式完成:

    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
    

    所有4个变体都是here

1 个答案:

答案 0 :(得分:6)

关于#1:Python不做这样的事情。请注意,您编写的fast_sqrt函数(即在任何装饰器之前)不是生成器函数,协程函数,任务或任何您想要调用它的函数。这是一个普通的函数同步运行并返回return语句后写的内容。根据{{​​1}}的存在,会发生非常不同的事情。只是运气不好导致同样的错误。

  1. 没有装饰器,@coroutine就像普通函数一样运行并返回float的未来(,无论上下文)。该未来由fast_sqrt(x)消耗,future = yield from ...为浮点数(没有future方法)。

  2. 使用装饰器,调用result将通过f(x)创建的包装函数。此包装函数调用@coroutine并使用fast_sqrt构造为您解包生成的未来。因此,这个包装函数本身就是一个协程。因此,yield from <future>等待 协程,并再次为future = yield from ...留下浮动。

  3. 关于#2,future 工作(如上所述,你在使用未修饰的yield from <future>时使用它),你也可以写:

    fast_sqrt

    (模块化它对{​​{1}}不起作用,并且没有额外的异步,因为从future = yield from coro_returning_a_future(x) res = yield from future 返回时已经完成了未来。)

    你的核心问题似乎是你对协同程序和未来感到困惑。你的sqrt实现都试图成为导致未来的异步任务。 根据我有限的经验,这不是通常编写asyncio代码的方式。它允许您将未来的构造和未来的计算结合到两个独立的异步任务中。但你不这样做(你回归已经完成的未来)。大多数情况下,这不是一个有用的概念:如果你必须异步进行一些计算,你要么把它写成一个协同程序(可以暂停)你将它推入另一个线程并使用fast_sqrt与之通信。不是两个。

    要使平方根计算异步,只需编写一个执行计算的常规协程并coro_returning_a_future结果(yield from <future>装饰器将return转换为异步运行的任务,并且可以等待)。

    coroutine