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 アプリケーションでは、どちらの形も書く必要はありません。