12

次のプログラムを考えてみましょう (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 つの (関連する) 質問があります。

  1. デコレータが on であってもfast_sqrt、Python は で作成された Future をfast_sqrt完全に最適化するようで、プレーンfloatが返されます。その後run_test()yield from

  2. 例外を発生させる値を取得するために評価future.result()する必要があるのはなぜですか? ドキュメントには、run_test「未来が完了するまでコルーチンを一時停止し、その後、未来の結果を返すか、例外を発生させる」と書かれています。フューチャの結果を手動で取得する必要があるのはなぜですか?yield from <future>

ここに私が得るものがあります:

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.sleepinは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 つのバリアントはすべてここにあります。

4

1 に答える 1

6

#1について:Pythonはそのようなことはしません。fast_sqrt作成した関数 (つまり、デコレーターの前) は、ジェネレーター関数、コルーチン関数、タスク、またはそれを呼び出したいものではないことに注意してください。これは、同期的に実行され、ステートメントの後に記述したものを返す通常の関数ですreturn。の存在に応じて@coroutine、非常に異なることが起こります。どちらも同じエラーになるのは運が悪いだけです。

  1. デコレータがなければfast_sqrt(x)、通常の関数のように実行され、future の float を返します ( context に関係なく)。その未来は によって消費され、 float (メソッドを持たない)future = yield from ...を残します。futureresult

  2. デコレータを使用すると、呼び出しf(x)によって作成されたラッパー関数を通過します@coroutine。このラッパー関数はfast_sqrt、構造を使用して、結果の未来を呼び出してアンパックしますyield from <future>。したがって、このラッパー関数自体がコルーチンです。したがって、そのコルーチンをfuture = yield from ...待機し、再びフロートを残します。future

#2に関してyield from <future> 、機能します(上記で説明したように、 undecorated を使用するときに使用していますfast_sqrt)。次のように書くこともできます:

future = yield from coro_returning_a_future(x)
res = yield from future

(書かれているように機能しないモジュロでありfast_sqrt、から返されるまでに未来がすでに完了しているため、追加の非同期性が得られませんcoro_returning_a_future。)

あなたの中心的な問題は、コルーチンと先物を混同しているようです。両方の sqrt 実装が非同期タスクになろうとして、先物が発生します。私の限られた経験から、それは通常 asyncio コードを書く方法ではありません。これにより、未来の構築と、未来が表す計算の両方を、2 つの独立した非同期タスクにまとめることができます。しかし、あなたはそうしません (すでに完成した未来を返します)。そして、ほとんどの場合、これは有用な概念ではありません。非同期で計算を行う必要がある場合は、コルーチン (一時停止可能) として記述する別のスレッドにプッシュして を使用して通信し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)
于 2013-12-22T12:36:27.800 に答える