8.10. Awaitable-класи

8.10.1. Що таке awaitable

Коли корутина пише await x, мова запитує x, як на нього чекати. Будь-який об’єкт, що знає відповідь, є awaitable. Є два види:

  • Об’єкти корутин, що повертають функції async def. Кожного разу виклик send_request() створює один з них — об’єкт корутини, а не результат функції. Запис await send_request() — ось що насправді виконує тіло.

  • Об’єкти класу, що визначає метод __await__. async def — скорочення для першого виду; __await__ — ручний спосіб створити другий вид.

За замовчуванням код застосунку використовує перший вид. Другий вид існує для рідкісного випадку, коли сам об’єкт повинен бути тим, чого await-ає викликач, а не результатом виклику методу на ньому.

8.10.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. Коли форма класу виправдовує себе

Коли об’єкт має бути чимось, що застосунок передає навколо — не функцією, яку викликач викликає для отримання корутини — форма класу є єдиним варіантом:

  • Future-подібне значення, яке застосунок створює, передає виробнику та await-ає пізніше для отримання виробленого результату.

  • Власний примітив, побудований поверх існуючого, де природним API є await my_thing, а не await my_thing.wait(). Клас asyncio.Task сам є вбудованим прикладом — запис await task працює, оскільки Task визначає __await__.

Для всього, що підходить під форму викликати функцію, отримати корутину, await-нути її, async def перемагає.

8.10.4. Деталі протоколу

__await__ — це звичайний метод (не async def), що повертає ітератор. Написання його як генератора — з хоча б одним yield у тілі — автоматично робить його таким. Кожен yield призупиняє корутину, що await-ає об’єкт, і передає керування назад циклу подій; генератор відновлюється наступного разу, коли цикл планує задачу. Голий return (або вихід з кінця) завершує очікування; те, що виробляє return, стає значенням виразу await.

На практиці рідкісний клас, що це робить, передає yielding існуючому 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 ніколи не потребують написання жодної з цих форм.