8.10. awaitable なクラス¶
8.10.1. awaitable とは何か¶
コルーチンが await x と書くと、言語は x にどう待機すればよいかを尋ねます。その答え方を知っているオブジェクトはすべて awaitable です。次の 2 種類があります:
async def関数が返すコルーチンオブジェクト。send_request()を呼び出すと、そのたびにこれが 1 つ生成されます。つまり生成されるのはコルーチン オブジェクト であって、関数の結果ではありません。実際に本体を実行するのはawait send_request()と書いたときです。__await__メソッドを定義したクラスのオブジェクト。async defは 1 つ目の種類を作る省略記法であり、__await__は 2 つ目の種類を手動で作る方法です。
アプリケーションコードは既定では 1 つ目の種類を使います。2 つ目の種類は、メソッドを呼び出した結果ではなく オブジェクトそのもの を呼び出し側が await する対象にする必要がある、まれなケースのために存在します。
8.10.2. 2 つの形を並べて比較する¶
同じロジックをコルーチンとして、また awaitable なクラスとして書いたものです:
import asyncio
# async def -- almost always the right choice
async def yield_then_return(value):
await asyncio.sleep_ms(0)
return value
# awaitable class -- equivalent, rarely written
class YieldThenReturn:
def __init__(self, value):
self._value = value
def __await__(self):
yield # let the loop run once
return self._value # value of the ``await`` expression
どちらも同じように await できます:
async def main():
a = await yield_then_return(1)
b = await YieldThenReturn(2)
print(a, b) # prints: 1 2
async def の形のほうが短く、通常の Python のように読め、サスペンドと再開の管理をすべて言語に任せられます。この例では、クラスの形に追加の利点はありません。
8.10.3. クラスの形が真価を発揮するとき¶
オブジェクトが、呼び出し側がコルーチンを得るために呼び出す関数ではなく、アプリケーションが受け渡しする何かそのもので なければならない 場合、クラスの形が唯一の選択肢です:
アプリケーションが作成してプロデューサーに渡し、後で生成された結果を取り出すために
awaitする future ライク な値。既存のプリミティブの上に構築されたカスタムプリミティブで、自然な API が
await my_thing.wait()ではなくawait my_thingであるもの。asyncio.Taskクラス自体が組み込みの例です。await taskが機能するのは、Taskが__await__を定義しているためです。
関数を呼び出し、コルーチンを得て、それを await する という形に当てはまるものについては、async def が勝ります。
8.10.4. プロトコルの詳細¶
__await__ は通常のメソッド(async def ではありません)で、イテレータを返します。これをジェネレータとして書く(本体に少なくとも 1 つの yield を含める)と、自動的にイテレータになります。各 yield は、そのオブジェクトを await しているコルーチンをサスペンドし、制御をイベントループに戻します。ジェネレータは、ループが次にそのタスクをスケジュールしたときに再開します。単なる return(または末尾に到達すること)は待機を終了させ、return が生成したものが await 式の値になります。
実際には、これを行うまれなクラスは、ループ自体を駆動するのではなく、yield を既存の awaitable に委譲します:
class WaitForEvent:
def __init__(self, event):
self._event = event
def __await__(self):
yield from self._event.wait().__await__()
return self._event
yield from は、実際のサスペンドのために、基盤となるイベントの __await__ を再利用します。ほとんどの asyncio アプリケーションでは、どちらの形も書く必要はありません。