Pourquoi la programmation asynchrone change tout
Tu dĂ©veloppes en Python et tu te demandes pourquoi certaines applications mettent 30 secondes Ă rĂ©pondre alors qu’elles ne font « presque rien » ? Le coupable est souvent l’exĂ©cution synchrone : ton programme attend bĂȘtement qu’une opĂ©ration lente (requĂȘte HTTP, lecture fichier, appel base de donnĂ©es) se termine avant de passer Ă la suite.
La programmation asynchrone avec async/await rĂ©sout ce problĂšme : elle permet Ă ton code de continuer Ă s’exĂ©cuter pendant qu’il attend des opĂ©rations lentes. RĂ©sultat : des applications jusqu’Ă 10 fois plus rapides sans ajouter un seul thread.
Dans ce tutoriel, tu vas apprendre les bases de la programmation asynchrone en Python avec des exemples concrets et progressifs. Commençons !
Qu’est-ce que la programmation asynchrone ?
Imagine un chef cuisinier qui doit prĂ©parer un repas complet. En mode synchrone, il met l’eau Ă bouillir, reste devant la casserole jusqu’Ă Ă©bullition, puis prĂ©pare la sauce, attend qu’elle mijote, puis coupe les lĂ©gumes. Il passe 80 % de son temps Ă attendre.
En mode asynchrone, il met l’eau Ă bouillir et, pendant ce temps, coupe les lĂ©gumes. Quand l’eau bout, il laisse les pĂątes cuire et, pendant ce temps, prĂ©pare la sauce. Il fait progresser plusieurs tĂąches sans jamais attendre inactivement.
C’est exactement ce que fait async/await en Python : quand une opĂ©ration est en attente (I/O), le programme exĂ©cute autre chose au lieu de bloquer.
Les bases : async et await
Commençons par le squelette théorique avant de passer à des exemples pratiques.
Une fonction asynchrone se déclare avec async def au lieu de def :
import asyncio
async def dire_bonjour():
print("Bonjour...")
await asyncio.sleep(1) # Simule une attente (I/O)
print("...le monde !")
# Pour exécuter une fonction async :
asyncio.run(dire_bonjour())
Les mots-clés essentiels :
async defâ dĂ©clare une fonction asynchrone (une « coroutine »)awaitâ marque un point d’attente : le programme peut suspendre cette tĂąche et en exĂ©cuter une autreasyncio.run()â point d’entrĂ©e qui lance la boucle d’Ă©vĂ©nements
Premier exemple : synchrone vs asynchrone
Prenons deux fonctions qui « attendent » 2 secondes chacune (simulant une requĂȘte rĂ©seau) :
Version synchrone (lent)
import time
def operation_lente(n):
print(f" Opération {n} commence...")
time.sleep(2) # Bloque tout !
print(f" Opération {n} terminée.")
return n * 10
def main_sync():
debut = time.time()
resultats = []
for i in range(4):
resultats.append(operation_lente(i))
duree = time.time() - debut
print(f"Synchrone : {duree:.1f} secondes")
return resultats
main_sync()
# Sortie : Synchrone : 8.0 secondes
Version asynchrone (rapide)
import asyncio
async def operation_lente_async(n):
print(f" Opération {n} commence...")
await asyncio.sleep(2) # Laisse la main Ă d'autres tĂąches
print(f" Opération {n} terminée.")
return n * 10
async def main_async():
debut = time.time()
# Lance toutes les opérations en parallÚle
resultats = await asyncio.gather(
operation_lente_async(0),
operation_lente_async(1),
operation_lente_async(2),
operation_lente_async(3),
)
duree = time.time() - debut
print(f"Asynchrone : {duree:.1f} secondes")
return resultats
asyncio.run(main_async())
# Sortie : Asynchrone : 2.0 secondes
Avec asyncio.gather(), les 4 opĂ©rations s’exĂ©cutent en parallĂšle et le temps total passe de 8 secondes Ă seulement 2 secondes. Le gain est de 75 % !
Cas pratique : requĂȘter plusieurs API simultanĂ©ment
Voici un exemple concret qui utilise aiohttp pour appeler 5 API en mĂȘme temps :
import asyncio
import aiohttp
import time
async def recuperer_url(session, url, n):
print(f"đ RequĂȘte {n} : {url}")
async with session.get(url) as response:
data = await response.text()
taille = len(data)
print(f"â
RequĂȘte {n} terminĂ©e : {taille} caractĂšres")
return {"url": url, "taille": taille}
async def main():
urls = [
"https://jsonplaceholder.typicode.com/posts",
"https://jsonplaceholder.typicode.com/comments",
"https://jsonplaceholder.typicode.com/albums",
"https://jsonplaceholder.typicode.com/photos",
"https://jsonplaceholder.typicode.com/todos",
]
async with aiohttp.ClientSession() as session:
taches = [recuperer_url(session, url, i) for i, url in enumerate(urls)]
resultats = await asyncio.gather(*taches)
total = sum(r["taille"] for r in resultats)
print(f"\nđ Total : {total} caractĂšres rĂ©cupĂ©rĂ©s depuis {len(urls)} APIs")
return resultats
debut = time.time()
asyncio.run(main())
duree = time.time() - debut
print(f"â±ïž Temps total : {duree:.2f} secondes")
Si chaque requĂȘte prenait 1 seconde en synchrone, le total serait de 5 secondes. En asynchrone, le temps total est celui de la plus lente des requĂȘtes (environ 1 seconde).
Gérer les erreurs avec try/await/except
La gestion d’erreurs en asynchrone utilise la mĂȘme syntaxe familiĂšre :
import asyncio
async def requete_risquee(n):
if n == 3:
raise ValueError(f"Erreur simulĂ©e sur la requĂȘte {n}")
await asyncio.sleep(1)
return f"Résultat {n}"
async def main():
taches = [requete_risquee(i) for i in range(5)]
resultats = []
for coro in asyncio.as_completed(taches):
try:
resultat = await coro
resultats.append(resultat)
except ValueError as e:
print(f"â ïž Exception capturĂ©e : {e}")
print(f"Résultats obtenus : {resultats}")
asyncio.run(main())
Tu remarqueras qu’avec asyncio.as_completed(), tu rĂ©cupĂšres les rĂ©sultats au fur et Ă mesure qu’ils arrivent (pas besoin d’attendre la fin de toutes les tĂąches).
Limiter le nombre de tĂąches concurrentes
Si tu lances 1000 requĂȘtes simultanĂ©es, tu vas saturer ta connexion rĂ©seau (et l’API que tu appelles risque de te blacklister). La solution : un Semaphore (ou async with + limite) :
import asyncio
# Limiteur : 3 requĂȘtes simultanĂ©es maximum
semaphore = asyncio.Semaphore(3)
async def requete_limitee(n):
async with semaphore: # Attend si 3 requĂȘtes sont dĂ©jĂ en cours
print(f"đ DĂ©marrage requĂȘte {n}")
await asyncio.sleep(1)
print(f"â
Fin requĂȘte {n}")
return n
async def main():
# Lance 10 requĂȘtes, mais jamais plus de 3 Ă la fois
taches = [requete_limitee(i) for i in range(10)]
resultats = await asyncio.gather(*taches)
print(f"Tous les résultats : {resultats}")
asyncio.run(main())
Tu verras que les requĂȘtes dĂ©marrent par groupes de 3, chaque groupe attendant qu’une place se libĂšre. C’est indispensable dĂšs que tu travailles avec des API externes.
Les piÚges à éviter
La programmation asynchrone en Python a quelques subtilités :
â time.sleep() dans une fonction async â
time.sleep()bloque tout le programme, mĂȘme dans une coroutine. Utilise toujoursawait asyncio.sleep().â Oublier await â Si tu Ă©cris
ma_fonction_async()sansawait, tu obtiens un objet coroutine, pas le rĂ©sultat. C’est une erreur frĂ©quente.â MĂ©langer code synchrone et asynchrone â Appeler
asyncio.run()plusieurs fois dans le mĂȘme script peut causer des erreurs. Utilise une seule boucle d’Ă©vĂ©nements.â OpĂ©rations CPU-intensive â L’asynchrone est fait pour les opĂ©rations I/O (rĂ©seau, fichiers, BDD). Pour du calcul pur, utilise
multiprocessingouconcurrent.futures.
Quand utiliser async/await (et quand éviter)
| â Ă faire avec async | â Pas adaptĂ© |
|---|---|
| RequĂȘtes HTTP / API REST | Calculs lourds (traitement images, ML) |
| Lecture/écriture fichiers multiples | Opérations synchrones bloquantes (input utilisateur) |
| RequĂȘtes base de donnĂ©es | Code sĂ©quentiel simple (pas d’attente) |
| Web scraping de plusieurs pages | BibliothĂšques non async (certaines vieilles libs) |
| Websockets / temps réel | Scripts courts à usage unique |
Conclusion et prochaines étapes
La programmation asynchrone avec async/await est l’un des super-pouvoirs de Python moderne. Elle te permet d’Ă©crire des applications plus rapides, plus rĂ©actives et plus efficaces sans complexitĂ© supplĂ©mentaire â juste en comprenant quand et comment dĂ©lĂ©guer l’attente.
Commence par utiliser asyncio.gather() dans tes prochains projets qui font des appels rĂ©seau. C’est le plus simple Ă intĂ©grer et le gain est immĂ©diat.
đ MaĂźtrise les fondamentaux de Python â Si ce tutoriel t’a aidĂ©, le cours complet Python : Les Fondamentaux couvre tout ce qu’il faut pour devenir autonome en programmation Python : variables, fonctions, POO, modules et projets pratiques. DĂ©couvrir le cours â
Photo de Julia M Cameron sur Pexels