考虑以下程序(在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())
我有2个(相关)问题:
即使启动了装饰器fast_sqrt
,Python似乎也fast_sqrt
完全消除了创建的Future ,并float
返回了plain .然后在炸毁run_test()
的yield from
为什么我需要评估future.result()
在run_test
检索火的值例外呢?该文件说,yield from
"暂停协程,直到将来完成,然后返回未来的结果,或将引发异常".为什么我需要手动解决未来的结果?
这是我得到的:
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.4142135623730951exception: 'float' object has no attribute 'result' Future result: 1.4142135623730951 exception: negative number Future 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个变种都在这里.
关于#1:Python没有这样的事情.请注意,fast_sqrt
您编写的函数(即在任何装饰器之前)不是生成器函数,协程函数,任务或您想要调用的任何函数.它是一个普通的函数同步运行并返回你在return
语句后写的内容.根据存在的@coroutine
不同,会发生很多不同的事情.运气不好导致同样的错误.
没有装饰器,fast_sqrt(x)
就像普通函数一样运行并返回float的未来(无论上下文如何).那个未来被消耗掉future = yield from ...
,留下future
一个浮动(没有result
方法).
使用装饰器,调用f(x)
将通过创建的包装函数进行@coroutine
.这个包装函数fast_sqrt
使用yield from <future>
构造为您调用和解包生成的未来.因此,这个包装函数本身就是一个协程.因此,future = yield from ...
等待该协程future
再次离开浮点数.
关于#2,yield from <future>
确实有效(如上所述,你在使用未修饰时使用它fast_sqrt
),你也可以写:
future = yield from coro_returning_a_future(x) res = yield from future
(Modulo它不能用于fast_sqrt
编写,并且不会获得额外的异步,因为未来已经从它返回时完成了coro_returning_a_future
.)
你的核心问题似乎是你混淆了协同程序和未来.你的sqrt实现都试图成为导致未来的异步任务.根据我有限的经验,这不是通常编写asyncio代码的方式.它允许您将未来的构造和未来的计算结合到两个独立的异步任务中.但你不这样做(你回归已经完成的未来).大多数情况下,这不是一个有用的概念:如果你必须异步进行一些计算,你可以将它写为协程(可以暂停)或者将其推送到另一个线程并使用它与之通信yield from <future>
.不是都.
要使平方根计算异步,只需编写一个执行计算和return
结果的常规协同程序(coroutine
装饰器将变为fast_sqrt
异步运行并可等待的任务).
@coroutine def fast_sqrt(x): if x >= 0: return math.sqrt(x) else: raise Exception("negative number") @coroutine # for documentation, not strictly necessary def slow_sqrt(x): yield from asyncio.sleep(1) if x >= 0: return math.sqrt(x) else: raise Exception("negative number") ... res = yield from f(x) assert isinstance(res, float)