8.10. Ожидаемые классы

8.10.1. Что такое ожидаемый объект

Когда корутина пишет await x, язык спрашивает у x, как его ожидать. Любой объект, который знает, как ответить, является ожидаемым. Существует два вида:

  • Объекты-корутины, которые возвращают функции async def. Вызов send_request() каждый раз создаёт один из них – объект корутины, а не результат функции. Запись await send_request() – это то, что на самом деле запускает тело.

  • Объекты класса, который определяет метод __await__. async def – это сокращённая запись для первого вида; __await__ – это ручной способ создать второй вид.

Прикладной код по умолчанию использует первый вид. Второй вид существует для редкого случая, когда сам объект должен быть тем, что вызывающая сторона awaits, а не результатом вызова метода у него.

8.10.2. Две формы рядом

Одна и та же логика, написанная как корутина и как ожидаемый класс:

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. Когда форма с классом оправдана

Когда объект должен быть чем-то, что приложение передаёт по кругу – а не функцией, которую вызывающая сторона вызывает, чтобы получить корутину – форма с классом является единственным вариантом:

  • Future-подобное значение, которое приложение создаёт, передаёт производителю и awaits позже, чтобы получить произведённый результат.

  • Пользовательский примитив, построенный поверх существующего, где естественным API является await my_thing, а не await my_thing.wait(). Сам класс asyncio.Task – встроенный пример: запись await task работает, потому что Task определяет __await__.

Для всего, что подходит под форму вызвать функцию, получить корутину, ожидать её, побеждает async def.

8.10.4. Детали протокола

__await__ – это обычный метод (не async def), который возвращает итератор. Если написать его как генератор – включив хотя бы один yield в тело – он автоматически станет итератором. Каждый yield приостанавливает корутину, которая awaits объект, и возвращает управление циклу событий; генератор возобновляется в следующий раз, когда цикл планирует задачу. Простой return (или выход за конец) завершает ожидание; то, что производит return, становится значением выражения await.

На практике редкий класс, который это делает, передаёт выдачу значений существующему ожидаемому объекту, а не управляет циклом сам:

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-приложений никогда не нужно писать ни одну из этих форм.