PEP 0492 は新しい__await__
マジックメソッドを追加します。このメソッドを実装するオブジェクトは未来のオブジェクトになり、await
を使用して待機できます。それは明確だ:
import asyncio
class Waiting:
def __await__(self):
yield from asyncio.sleep(2)
print('ok')
async def main():
await Waiting()
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
では、async def
ではなくasyncio.sleep
で定義された関数を呼び出したい場合はどうでしょうか。 __await__
はawait
関数ではないため、async
を使用できません。ネイティブコルーチンにはawait
式が必要なため、yield from
を使用できません。
async def new_sleep():
await asyncio.sleep(2)
class Waiting:
def __await__(self):
yield from new_sleep() # this is TypeError
await new_sleep() # this is SyntaxError
print('ok')
どうすれば解決できますか?
直接__await__()
呼び出しを使用:
async def new_sleep():
await asyncio.sleep(2)
class Waiting:
def __await__(self):
return new_sleep().__await__()
ソリューションは、Yury Selivanov( PEP 492 の作成者)が aioodbcライブラリ に対して推奨しました
ショートバージョン:_await foo
_はyield from foo.__await__()
で置き換えることができます
他の答えからのすべてのアイデアを組み合わせる-
最も単純なケースでは、他の待機可能な作品に委任するだけです:
_def __await__(self):
return new_sleep().__await__()
_
これは、___await__
_メソッドがイテレーターを返すため( PEP 492 を参照)、別の___await__
_のイテレーターを返すので問題ありません。
もちろん、これは、待機可能なオリジナルのサスペンション動作をまったく変更できないことを意味します。より一般的なアプローチは、await
キーワードをミラーリングし、_yield from
_を使用することです。これにより、複数のawaitableのイテレータを1つに結合できます。
_def __await__(self):
# theoretically possible, but not useful for my example:
#yield from something_else_first().__await__()
yield from new_sleep().__await__()
_
これが問題です:これは最初のバリアントとまったく同じことをしていません! _yield from
_は式なので、以前とまったく同じようにするには、その値も返す必要があります。
_def __await__(self):
return (yield from new_sleep().__await__())
_
これは、await
構文を使用して適切な委任を作成する方法を直接反映しています。
_ return await new_sleep()
_
余分なビット-これらの2つの違いは何ですか?
_def __await__(self):
do_something_synchronously()
return new_sleep().__await__()
def __await__(self):
do_something_synchronously()
return (yield from new_sleep().__await__())
_
最初のバリアントは単純な関数です。これを呼び出すと、_do_...
_が実行され、イテレータが返されます。 2つ目はジェネレーター関数です。それを呼び出しても、コードはまったく実行されません!返されたイテレータが初めて生成されるときのみ、_do_...
_が実行されます。これにより、次のような少し不自然な状況に違いが生じます。
_def foo():
tmp = Waiting.__await__()
do_something()
yield from tmp
_
__await__
内でネイティブコルーチンからの生成ができない理由を理解できませんでしたが、__await__
内でジェネレータコルーチンからの生成およびyield fromネイティブコルーチンその内部ジェネレータコルーチン。できます:
async def new_sleep():
await asyncio.sleep(2)
class Waiting:
def __await__(self):
@asyncio.coroutine
def wrapper(coro):
return (yield from coro)
return (yield from wrapper(new_sleep()))
__await__
関数内で待機するには、次のコードを使用します。
async def new_sleep():
await asyncio.sleep(1)
class Waiting:
def __await__(self):
yield from new_sleep().__await__()
print('first sleep')
yield from new_sleep().__await__()
print('second sleep')
return 'done'
デコレータを使用します。
def chain__await__(f):
return lambda *args, **kwargs: f(*args, **kwargs).__await__()
次に__await__
ネイティブコルーチンとして。
async def new_sleep():
await asyncio.sleep(2)
class Waiting:
@chain__await__
async def __await__(self):
return await new_sleep()
Mikhailのバージョンを次のように簡略化することもできます。
async def new_sleep():
await asyncio.sleep(2)
class Waiting:
def __await__(self):
async def wrapper():
await new_sleep()
print("OK")
return wrapper()
元のコードはPython 3.6で問題なく動作しました。
他の回答の修正は印象的であり、それらの一部が機能することは信じられませんでした(しかし、機能します!)。しかし、asyncio
モデルが変更され続けると、これらの修正は機能しなくなります。
私は3.7で動作する最小限の修正をラッパーなしで持っています:
_import asyncio
class Waiting:
def __await__(self):
yield from asyncio.sleep(2).__await__()
print('ok')
async def main():
await Waiting()
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
_
元のコードとの唯一の違いは、ラッパーがなくても.__await__()
をasyncio.sleep(2)
に追加することです。
_sync_await
_でawait
にしたいジェネレーターを囲むこの___await__
_ラッパーを使用することもできます。
_import asyncio
def sync_await(gen):
if hasattr(gen, '__await__'):
# 3.7, and user defined coroutines in 3.6
print('yield from gen.__await__()')
return (yield from gen.__await__())
else:
# 3.6, only native coroutines like asyncio.sleep()
print('yield from gen')
return (yield from gen)
class Waiting:
def __await__(self):
yield from sync_await(asyncio.sleep(2))
print('ok')
async def main():
await Waiting()
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
_
注-ラッパーはreturn (yield from ...)
を実行しませんが、_yield from
_-単純なジェネレーターイテレーター委譲です。