したがって、通常は、次のようなことを実行してコルーチンの結果を取得します。
_async def coro():
await asycnio.sleep(3)
return 'a value'
loop = asyncio.get_event_loop()
value = loop.run_until_complete(coro())
_
好奇心から、イベントループを使用せずにその値を取得する最も簡単な方法は何ですか?
[編集]
さらに簡単な方法は次のとおりです。
_async def coro():
...
value = asyncio.run(coro()) # Python 3.7+
_
しかし、_yield from
_(またはawait
)a coro()
を [〜#〜] js [ 〜#〜] ?そうでない場合、なぜですか?
ここで2つの質問があります。1つは、コルーチンが「トップレベル」で、またはより具体的には開発環境で待機することです。もう1つは、イベントループなしでコルーチンを実行することです。
最初の質問に関しては、Chrome Canary Dev Tools-イベントループとの独自の統合を介してそれを処理するツールによって可能であるように、これはPythonで確かに可能です。そして実際、IPython 7.0以降はasyncio natrative をサポートしており、予想どおり、トップレベルでawait coro()
を使用できます。
2番目の質問に関しては、イベントループなしで単一のコルーチンを駆動するのは簡単ですが、あまり役に立ちません。その理由を調べてみましょう。
コルーチン関数が呼び出されると、コルーチンオブジェクトが返されます。このオブジェクトは、そのsend()
メソッドを呼び出すことによって開始および再開されます。コルーチンがsuspendを決定すると(それがawait
sをブロックするため)、send()
が返されます。コルーチンがreturnを決定すると(最後に到達したか、明示的なreturn
が検出されたため)、StopIteration
属性を持つvalue
例外が発生します戻り値に設定します。このことを念頭に置くと、単一のコルーチンの最小ドライバーは次のようになります。
_def drive(c):
while True:
try:
c.send(None)
except StopIteration as e:
return e.value
_
これは単純なコルーチンに最適です。
_>>> async def pi():
... return 3.14
...
>>> drive(pi())
3.14
_
または、もう少し複雑なものでも:
_>>> async def plus(a, b):
... return a + b
...
>>> async def pi():
... val = await plus(3, 0.14)
... return val
...
>>> drive(pi())
3.14
_
しかし、まだ何かが欠けています-上記のコルーチンのどれも、それらの実行をsuspendしません。コルーチンが中断すると、他のコルーチンの実行が可能になり、イベントループが多数のコルーチンを一度に実行(表示)できるようになります。たとえば、asyncioにはsleep()
コルーチンがあり、待機すると、指定された期間実行が一時停止されます。
_async def wait(s):
await asyncio.sleep(1)
return s
>>> asyncio.run(wait("hello world"))
'hello world' # printed after a 1-second pause
_
ただし、drive
はこのコルーチンの実行に失敗しました:
_>>> drive(wait("hello world"))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in drive
File "<stdin>", line 2, in wait
File "/usr/lib/python3.7/asyncio/tasks.py", line 564, in sleep
return await future
RuntimeError: await wasn't used with future
_
何が起こったかというと、sleep()
が特別な「未来」オブジェクトを生成することによってイベントループと通信するということです。未来を待っているコルーチンは、未来が設定された後にのみ再開できます。 「本当の」イベントループは、未来が終了するまで他のコルーチンを実行することによってそうします。
これを修正するために、ミニイベントループで動作する独自のsleep
実装を作成できます。これを行うには、イテレータを使用してawaitableを実装する必要があります。
_class my_sleep:
def __init__(self, d):
self.d = d
def __await__(self):
yield 'sleep', self.d
_
コルーチンの呼び出し元には表示されないタプルを生成しますが、drive
(イベントループ)に何をすべきかを伝えます。 drive
とwait
は次のようになります。
_def drive(c):
while True:
try:
susp_val = c.send(None)
if susp_val is not None and susp_val[0] == 'sleep':
time.sleep(susp_val[1])
except StopIteration as e:
return e.value
async def wait(s):
await my_sleep(1)
return s
_
このバージョンでは、wait
は期待どおりに機能します。
_>>> drive(wait("hello world"))
'hello world'
_
コルーチンを駆動する唯一の方法はdrive()
を呼び出すことであり、これも単一のコルーチンをサポートするため、これはまだあまり役に立ちません。したがって、単にtime.sleep()
を呼び出して1日だけ呼び出す同期関数を作成することもできます。コルーチンが非同期プログラミングのユースケースをサポートするには、drive()
が次のことを行う必要があります。
これは、asyncioイベントループが他の多くの機能と共にテーブルにもたらすものです。イベントループを最初から構築することは、デビッドビーズリーによる this talk で実演されており、ライブの聴衆の前で機能的なイベントループを実装しています。
少し調べてみたところ、コルーチンをグローバルに実行する最も簡単な解決策がわかったと思います。
もしあなたが>>> dir(coro)
Pythonは以下の属性を出力します:
['__await__', '__class__', '__del__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'cr_await', 'cr_code', 'cr_frame', 'cr_Origin', 'cr_running', 'send', 'throw']
いくつかの属性が際立っています、つまり:
[
'__await__',
'close',
'cr_await',
'cr_code',
'cr_frame',
'cr_Origin',
'cr_running',
'send',
'throw'
]
yield(yield)は何をしますか? を読んだ後、一般的にジェネレーターがどのように機能するかを読んだ後、sendメソッドがキーでなければならないことを理解しました。
だから私はしようとしました:
>>> the_actual_coro = coro()
<coroutine object coro at 0x7f5afaf55348>
>>>the_actual_coro.send(None)
そして興味深いエラーが発生しました:
Original exception was:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration: a value
実際に戻り値を例外で返しました!
だから私は非常に基本的なループを考えました、まあ、それはランナーのようなもので、次のように実装できます:
def run(coro):
try:
coro.send(None)
except StopIteration as e:
return e.value
これで、コルーチンを同期関数で実行できます。グローバルに実行することもできますが、そうすることはお勧めしません。しかし、コルーチンを実行するために使用できる最も簡単で最低のレベルを知ることは興味深いです
>>> run(coro())
'a value'
ただし、これはNone
に待機する必要があるものがある場合にcoro
を返します(これは、コルーチンであるということの本質です)。
これはおそらく、イベントループがコルーチン(coro.cr_frame.f_locals
)それらを先物に割り当て、それらを別々に処理することによって?私の単純なrun
関数は明らかに提供していません。その点で私は間違っているかもしれません。だから私が間違っているなら誰かが私を訂正してください。
コルーチンはイベントループでのみ実行できるため、イベントループを使用せずにコルーチンの値を取得する方法はありません。
ただし、明示的にrun_until_complete
に渡さなくても、コルーチンを実行できます。イベントループの実行中に、値が取得されるのを待つだけです。例えば:
import asyncio
async def test():
await asyncio.sleep(1)
return 'a value'
async def main():
res = await test()
print('got value from test() without passing it to EL explicitly')
print(res)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())