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. Chiamaresend_request()ne produce uno ogni volta – l”oggetto coroutine, non il risultato della funzione. Scrivereawait 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
awaitin seguito per recuperare il risultato prodotto.Una primitiva personalizzata costruita sopra una esistente in cui l’API naturale è
await my_thingpiuttosto cheawait my_thing.wait(). La classeasyncio.Taskstessa è un esempio integrato – scrivereawait taskfunziona perchéTaskdefinisce__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.