In Python possiamo tramite le coroutine implementare comportamenti asincroni secondo il classico modello “async await” in modo semplice e immediato. Scopriamo assieme come!
Ciao mi chiamo Lorenzo Neri e sono un informatico: realizzo contenuti per aiutare le persone a padroneggiare l’arte del nuovo millennio, ovvero l’informatica!
Il comportamenti asincroni, classicissimi in strutture client/server, possono essere implementate anche all’interno di script Python senza troppe complicazioni.
Ciò di cui abbiamo bisogno sono proprio le coroutine: Scopriamole.
Cosa sono le coroutine in Python
Prendiamo una funzione. Anzi, creiamo una funzione. Il risultato che però ci restituisce, non è immediato. Per intenderci, facciamo un esempio terra-terra.
Siamo un client: mandiamo una richiesta ad un server. Passerà del tempo prima di ricevere una risposta da esso.
Da qui, il concetto di asincronia.
Da qui, il fatto che dobbiamo aspettare.
Quindi, una volta aspettato il tempo necessario a ricevere una risposta, la restituiamo al client.
Le coroutine, sono proprio ciò che ho detto fino ad ora.
“Le coroutine sono funzioni Python che permettono, tramite un processo di attesa, di implementare il meccanismo async await”
Esistono diversi modi di implementarle, ma alla base di tutte le modalità, ci serve la libreria asyncio.
Prima di addentrarci nelle varie modalità, faccio l’esempio più semplice di tutti.
Abbiamo una funzione che, ci stampa un “ciao”, poi mette in attesa il client di un secondo per stampare il suo argomento.
Ciò che dobbiamo fare è pressappoco questo:
# importiamo la libreria
import asyncio
# dichiaro la funzione ma che sia async
async def funzione_asincrona(parametro):
print("ciao")
# metto in attesa
await asyncio.sleep(1)
# stampo il parametro
print(parametro)
Tuttavia per invocarla, non basta invocarla come siamo abituati di solito.
Dobbiamo farlo, sempre tramite la libreria asyncio, più o meno così:
>>> asyncio.run(funzione_asincrona("Lorenzo"))
ciao
Lorenzo
Awaitable
La parola “await” usata all’interno del codice è molto “forte” in termini di presenza: questo perché quando creiamo una funzione asincrona, creiamo un oggetto “awaitable”, per questo dobbiamo eseguirle tramite il metodo “run”.
Questi oggetti, o meglio: funzioni asincrone di tipo awaitable, le possiamo dividere in tre tipi.
1. Coroutine async await
Le coroutine le abbiamo viste fino a poco fa. Sono funzioni dichiarate tramite la parola chiave “async” e sono oggetti di tipo awaitable.
Passiamo oltre.
2. Task async await
I task sono un tipo diverso di coroutine e il loro meccanismo di async await viene usato per eseguire corotuine in maniera concorrente.
Quando “wrappiamo” una coroutine in un task con il metodo “create_task” della libreria asyncio, ciò che succede è che la coroutine è “schedulata” per essere lanciata il prima possibile.
Facciamo un esempio:
import asyncio
async def innestata():
return 22
async def main():
# creiamo il task
task = asyncio.create_task(innestata())
# la variabile "task" la possiamo usare sia per cancellare "innestata"
# sia per aspettare finché non è completata l'esecuzione
await task
asyncio.run(main())
3. Future async await
I future sono degli oggetti di tipo awaitable a basso livello che rappresentato il risultato di un’operazione asincrona.
Che significa ‘sta frasona in tecnichese?
Quando un oggetto di tipo “future” è “aspettato” ovvero “awaited”, significa che la coroutine aspetterà finché il future avrà terminato la sua esecuzione.
Capisco che questa parte è abbastanza complessa, perciò facciamo un esempio per chiarire:
async def main():
# modalità 1
await funzione_che_torna_un_oggetto_future()
# modalità 2
await asyncio.gather(
funzione_che_torna_un_oggetto_future(),
una_coroutine_python()
)
Un altro esempio che mi sento di lasciarti per completezza, è loop.run_in_executor().