8.10. Clases awaitable

8.10.1. Qué es un awaitable

Cuando una corrutina escribe await x, el lenguaje le pregunta a x cómo esperarlo. Cualquier objeto que sepa responder es awaitable. Hay dos tipos:

  • Los objetos corrutina que devuelven las funciones async def. Llamar a send_request() produce uno de estos cada vez – el objeto corrutina, no el resultado de la función. Escribir await send_request() es lo que realmente ejecuta el cuerpo.

  • Los objetos de una clase que define un método __await__. async def es la forma abreviada del primer tipo; __await__ es la forma manual de crear el segundo tipo.

El código de la aplicación usa el primer tipo de forma predeterminada. El segundo tipo existe para el raro caso en que el propio objeto deba ser aquello que el llamador hace await, en lugar del resultado de llamar a un método sobre él.

8.10.2. Las dos formas, lado a lado

La misma lógica escrita como corrutina y como clase 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

Ambas pueden ser objeto de await de la misma manera:

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

La forma async def es más corta, se lee como Python normal y deja que el lenguaje gestione toda la contabilidad de suspensión y reanudación. La forma de clase no obtiene nada adicional en este ejemplo.

8.10.3. Cuándo se justifica la forma de clase

Cuando el objeto tiene que ser algo que la aplicación pasa por ahí – y no una función que el llamador invoca para obtener una corrutina – la forma de clase es la única opción:

  • Un valor similar a un future que la aplicación crea, entrega a un productor y luego espera con await para recuperar el resultado producido.

  • Una primitiva personalizada construida sobre una existente donde la API natural es await my_thing en lugar de await my_thing.wait(). La propia clase asyncio.Task es un ejemplo integrado – escribir await task funciona porque Task define __await__.

Para cualquier cosa que encaje en la forma llamar a una función, obtener una corrutina, esperarla, async def gana.

8.10.4. Los detalles del protocolo

__await__ es un método normal (no async def) que devuelve un iterador. Escribirlo como generador – incluyendo al menos un yield en el cuerpo – lo convierte en uno automáticamente. Cada yield suspende la corrutina que hace await sobre el objeto y devuelve el control al bucle de eventos; el generador se reanuda la próxima vez que el bucle planifica la tarea. Un return simple (o llegar al final) finaliza la espera; lo que produzca el return se convierte en el valor de la expresión await.

En la práctica, la rara clase que hace esto delega el ceder en un awaitable existente en lugar de impulsar el bucle por sí misma:

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

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

El yield from reutiliza el __await__ del evento subyacente para la suspensión real. La mayoría de las aplicaciones asyncio nunca necesitan escribir ninguna de las dos formas.