8.10. Classi awaitable

8.10.1. Cos’è un oggetto awaitable

Quando una coroutine scrive await x, il linguaggio chiede a x come attenderlo. Qualsiasi oggetto che sa rispondere è awaitable. Ne esistono due tipi:

  • Gli oggetti coroutine restituiti dalle funzioni async def. Chiamare send_request() ne produce uno ogni volta – l”oggetto coroutine, non il risultato della funzione. Scrivere await send_request() è ciò che ne esegue effettivamente il corpo.

  • Gli oggetti di una classe che definisce un metodo __await__. async def è la forma abbreviata per il primo tipo; __await__ è il modo manuale per creare il secondo tipo.

Il codice applicativo usa il primo tipo per impostazione predefinita. Il secondo tipo esiste per il raro caso in cui l”oggetto stesso debba essere ciò che il chiamante attende con await, non il risultato della chiamata a un suo metodo.

8.10.2. Le due forme a confronto

La stessa logica scritta come coroutine e come 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

Entrambe possono essere attese con await allo stesso modo:

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

La forma async def è più breve, si legge come normale Python e lascia che sia il linguaggio a gestire tutta la contabilità di sospensione e ripresa. In questo esempio la forma a classe non offre nulla in più.

8.10.3. Quando la forma a classe si guadagna il suo posto

Quando l’oggetto deve essere qualcosa che l’applicazione fa circolare – non una funzione che il chiamante invoca per ottenere una coroutine – la forma a classe è l’unica opzione:

  • Un valore future-like che l’applicazione crea, consegna a un produttore e attende con await in seguito per recuperare il risultato prodotto.

  • Una primitiva personalizzata costruita sopra una esistente in cui l’API naturale è await my_thing piuttosto che await my_thing.wait(). La classe asyncio.Task stessa è un esempio integrato – scrivere await task funziona perché Task definisce __await__.

Per qualsiasi cosa che si adatti alla forma chiama una funzione, ottieni una coroutine, attendila, async def vince.

8.10.4. I dettagli del protocollo

__await__ è un metodo normale (non async def) che restituisce un iteratore. Scriverlo come generatore – includendo almeno uno yield nel corpo – lo rende tale automaticamente. Ogni yield sospende la coroutine che attende l’oggetto con await e restituisce il controllo all’event loop; il generatore riprende la volta successiva in cui il loop pianifica il task. Un return semplice (o l’arrivo alla fine) conclude l’attesa; qualsiasi cosa il return produca diventa il valore dell’espressione await.

In pratica la rara classe che fa questo delega l’emissione di yield a un oggetto awaitable esistente invece di guidare il loop autonomamente:

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

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

Il yield from riutilizza l”__await__ dell’evento sottostante per la sospensione vera e propria. La maggior parte delle applicazioni asyncio non ha mai bisogno di scrivere nessuna delle due forme.