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().

2 commenti
Ciao,
ho letto e riletto la spiegazione ma … non riesco a interpretarla nel pezzo di programma che ho trovato su github, che funziona ma non capisco molto… o meglio, avevo intuito che facesse delle cose a step ma non ho capito i comandi fanno iniziare una parte e la fanno finire per poi passare ad il prossimo step… fino a concludersi.
E’ più facile elencare il programma, tanto è piccoletto ma molto complesso per me :
import aiohttp
import asyncio
from renault_api.renault_client import RenaultClient
async def main():
async with aiohttp.ClientSession() as websession:
client = RenaultClient(websession=websession, locale=”fr_FR”)
await client.session.login(‘email’, ‘password’)
print(f”Accounts: {await client.get_person()}”) # List available accounts, make a note of kamereon account id
account_id = “Your Kamereon account id”
account = await client.get_api_account(account_id)
print(f”Vehicles: {await account.get_vehicles()}”) # List available vehicles, make a note of vehicle VIN
vin = “Your vehicle VIN”
vehicle = await account.get_api_vehicle(vin)
print(f”Cockpit information: {await vehicle.get_cockpit()}”)
print(f”Battery status information: {await vehicle.get_battery_status()}”)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
ecco questo è il programma che da quello che ho capito si logga tramite le credenziali, poi va a reperire altre informazioni del veicolo (mi serve per leggere dei valori dall’automobile) ed infine mostra una serie di dati per poi terminare.
Secondo te c’è il modo di farlo ripartire ogni tot di tempo? Io ho provato con un while ma ovunque lo metta mi da errore e sto cercando in lungo ed in largo se esistono comandi per farlo ripartire, ma non capisco…
Hai modo di aiutarmi, anche solo di spiegarmi il concetto di ripetizione , se si può attuare.
grazie infinite comunque.
Buona serata
Mauro
Il problema che incontri è dovuto al fatto che asyncio lavora con un event loop e non puoi semplicemente usare un while tradizionale senza gestire correttamente le coroutine. Il tuo script esegue una serie di chiamate asincrone e poi termina, quindi per ripeterlo ogni tot secondi devi inserirlo in un loop asincrono.
Soluzione con asyncio.sleep()
Un buon approccio è eseguire main() all’interno di un loop infinito o finito, usando asyncio.sleep() per farlo ripartire dopo un certo intervallo di tempo.
Ecco come puoi farlo:
import aiohttp
import asyncio
from renault_api.renault_client import RenaultClient
async def fetch_vehicle_data():
async with aiohttp.ClientSession() as websession:
client = RenaultClient(websession=websession, locale=”fr_FR”)
await client.session.login(‘email’, ‘password’)
print(f”Accounts: {await client.get_person()}”) # List available accounts
account_id = “Your Kamereon account id”
account = await client.get_api_account(account_id)
print(f”Vehicles: {await account.get_vehicles()}”) # List available vehicles
vin = “Your vehicle VIN”
vehicle = await account.get_api_vehicle(vin)
print(f”Cockpit information: {await vehicle.get_cockpit()}”)
print(f”Battery status information: {await vehicle.get_battery_status()}”)
async def main():
while True:
await fetch_vehicle_data()
await asyncio.sleep(300) # Attendi 300 secondi (5 minuti) prima di ripetere
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Perché funziona?
fetch_vehicle_data() contiene la logica principale ed è asincrona.
main() esegue un loop infinito che chiama fetch_vehicle_data() e poi attende 5 minuti prima di ripetere.
asyncio.sleep(300) è usato per evitare che il loop consumi CPU inutilmente.
Se vuoi che si ripeta solo un certo numero di volte, puoi sostituire il while True con un contatore:
async def main():
for _ in range(10): # Ripeti 10 volte
await fetch_vehicle_data()
await asyncio.sleep(300)
Con questo metodo, puoi eseguire il codice in modo periodico senza bloccare l’event loop.