8.10. Classes awaitable

8.10.1. Ce qu’est un awaitable

Lorsqu’une coroutine écrit await x, le langage demande à x comment l’attendre. Tout objet qui sait répondre est awaitable. Il en existe deux sortes :

  • Les objets coroutine que renvoient les fonctions async def. Appeler send_request() en produit un à chaque fois – l”objet coroutine, et non le résultat de la fonction. Écrire await send_request() est ce qui exécute réellement le corps.

  • Les objets d’une classe qui définit une méthode __await__. async def est le raccourci pour la première sorte ; __await__ est la manière manuelle de créer la seconde.

Le code applicatif utilise la première sorte par défaut. La seconde existe pour le cas rare où l”objet lui-même doit être ce que l’appelant await, et non le résultat de l’appel d’une méthode sur lui.

8.10.2. Les deux formes côte à côte

La même logique écrite sous forme de coroutine et de classe 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

Les deux peuvent être attendues (await) de la même façon:

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

La forme async def est plus courte, se lit comme du Python ordinaire et laisse le langage gérer toute la comptabilité de suspension et de reprise. La forme par classe n’apporte rien de plus dans cet exemple.

8.10.3. Quand la forme par classe a sa raison d’être

Lorsque l’objet doit être quelque chose que l’application fait circuler – et non une fonction que l’appelant invoque pour obtenir une coroutine – la forme par classe est la seule option :

  • Une valeur de type future que l’application crée, remet à un producteur, puis attend (await) plus tard pour récupérer le résultat produit.

  • Une primitive personnalisée construite au-dessus d’une primitive existante, où l’API naturelle est await my_thing plutôt que await my_thing.wait(). La classe asyncio.Task elle-même en est un exemple intégré – écrire await task fonctionne parce que Task définit __await__.

Pour tout ce qui correspond à la forme appeler une fonction, obtenir une coroutine, l’attendre, async def l’emporte.

8.10.4. Les détails du protocole

__await__ est une méthode ordinaire (et non un async def) qui renvoie un itérateur. L’écrire sous forme de générateur – en incluant au moins un yield dans le corps – en fait automatiquement un. Chaque yield suspend la coroutine qui attend (await) l’objet et rend le contrôle à la boucle d’événements ; le générateur reprend la prochaine fois que la boucle planifie la tâche. Un return nu (ou le fait d’atteindre la fin) termine l’attente ; ce que produit le return devient la valeur de l’expression await.

En pratique, la rare classe qui procède ainsi délègue le yield à un awaitable existant plutôt que de piloter elle-même la boucle:

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

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

Le yield from réutilise le __await__ de l’événement sous-jacent pour la suspension proprement dite. La plupart des applications asyncio n’ont jamais besoin d’écrire l’une ou l’autre forme.