8.10. Classes aguardáveis

8.10.1. O que é um aguardável

Quando uma corrotina escreve await x, a linguagem pergunta a x como esperar por ele. Qualquer objeto que saiba responder é aguardável. Há dois tipos:

  • Os objetos corrotina que as funções async def retornam. Chamar send_request() produz um desses a cada vez – o objeto corrotina, não o resultado da função. Escrever await send_request() é o que de fato executa o corpo.

  • Objetos de uma classe que define um método __await__. async def é a forma abreviada do primeiro tipo; __await__ é a maneira manual de criar o segundo tipo.

O código da aplicação usa o primeiro tipo por padrão. O segundo tipo existe para o caso raro em que o próprio objeto precisa ser aquilo que o chamador awaits, e não o resultado de chamar um método sobre ele.

8.10.2. As duas formas lado a lado

A mesma lógica escrita como uma corrotina e como uma classe aguardável:

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

Ambas podem ser awaited da mesma forma:

async def main():
    a = await yield_then_return(1)
    b = await YieldThenReturn(2)
    print(a, b)                     # prints: 1 2

A forma async def é mais curta, lê-se como Python normal e deixa a linguagem gerenciar toda a contabilidade de suspensão e retomada. A forma de classe não ganha nada a mais neste exemplo.

8.10.3. Quando a forma de classe vale a pena

Quando o objeto precisa ser algo que a aplicação repassa por aí – e não uma função que o chamador invoca para obter uma corrotina – a forma de classe é a única opção:

  • Um valor semelhante a um future que a aplicação cria, entrega a um produtor e awaits mais tarde para recuperar o resultado produzido.

  • Uma primitiva personalizada construída sobre uma existente, em que a API natural é await my_thing em vez de await my_thing.wait(). A própria classe asyncio.Task é um exemplo embutido – escrever await task funciona porque Task define __await__.

Para qualquer coisa que se encaixe no formato chamar uma função, obter uma corrotina, aguardá-la, async def vence.

8.10.4. Os detalhes do protocolo

__await__ é um método regular (não async def) que retorna um iterador. Escrevê-lo como um gerador – incluindo ao menos um yield no corpo – o torna um automaticamente. Cada yield suspende a corrotina que awaits o objeto e devolve o controle ao event loop; o gerador retoma na próxima vez que o loop agendar a tarefa. Um return simples (ou chegar ao fim) finaliza a espera; o que quer que o return produza torna-se o valor da expressão await.

Na prática, a rara classe que faz isso delega a cessão (yielding) a um aguardável existente em vez de conduzir o loop ela mesma:

class WaitForEvent:
    def __init__(self, event):
        self._event = event

    def __await__(self):
        yield from self._event.wait().__await__()
        return self._event

O yield from reutiliza o __await__ do evento subjacente para a suspensão de fato. A maioria das aplicações asyncio nunca precisa escrever nenhuma das duas formas.