7.3 KiB
TP : Téléchargements synchrone et asynchrone (asyncio)
Informations générales
Cours : Python Avancé > Programmation concurrente > Tâches IO-bound concurrentes avec asyncio
Objectifs pédagogiques :
- Python avancé : programmation asynchrone
- Outils modernes (poetry, PyCharm)
- Bonnes pratiques de l'entreprise
Prérequis
Installation et configuration de l’environnement
- Faites
poetry installaprès import du projet (fichier lock déja présent), pour s'assurer que toutes les dépendances sont installées. - Assurez-vous que l'interpréteur Python du projet pointe bien vers l'environnement virtuel créé par Poetry, ce qui devrait être le cas par défaut (sinon cliquer en bas à droite sur le nom de l'interpréteur puis
Add new interpreter, ensuiteAdd local interpreter..., et sélectionnerpoetryavec Python3.13). - Attention, si vous êtes derrière un proxy, pour ce qui est de la version asynchrone en particulier, vous allez peut-être devoir passer le paramètre
proxy="http://host:port"à la méthodesession.get().
Connaissances préalables
- Connaissances de base en programmation
Énoncé
Contexte
Le téléchargement de plusieurs fichiers depuis Internet est une tâche (IO-bound) courante en développement. Cependant, la manière dont on s'y prend peut avoir un impact considérable sur la performance et la rapidité d'exécution d'un script. Cet exercice a pour but de comparer deux approches fondamentales : le téléchargement séquentiel (synchrone) et le téléchargement parallèle (asynchrone).
Les deux scripts Python permettent de télécharger la même liste d'images. Le premier le fera de manière traditionnelle, une image après l'autre. Le second utilisera les capacités asynchrones de Python pour effectuer les téléchargements en parallèle, afin d'observer le gain de performance.
Partie 1 : téléchargement synchrone
Dans cette première partie, vous allez créer un script qui télécharge une liste d'images de manière séquentielle en utilisant la bibliothèque requests.
graph TD
subgraph "Processus synchrone bloc par bloc"
direction TB
A[Début] --> B{Télécharger Image 1};
B --> C[Attente Réseau 1];
C --> D{Sauvegarder Image 1};
D --> E{Télécharger Image 2};
E --> F[Attente Réseau 2];
F --> G{Sauvegarder Image 2};
G --> H[...];
H --> I{Télécharger Image N};
I --> J[Attente Réseau N];
J --> K{Sauvegarder Image N};
K --> L[Fin];
end
Objectif
À partir du squelette de code fourni, écrire un script Python nommé sync_downloader.py qui :
- Télécharge une liste d'images depuis des URLs.
- Sauvegarde ces images dans un répertoire local.
- Mesure et affiche le temps total d'exécution.
Cahier des charges
-
Configuration de base :
- Utilisez la liste d'URLs et le chemin du répertoire de sortie fournis ci-dessous.
- Le script doit créer le répertoire de sortie s'il n'existe pas. Utilisez le module
pathlibpour une gestion propre des chemins.
-
Logging :
- Mettez en place un système de
loggingsimple pour afficher des informations pertinentes dans la console (début/fin du script, URL en cours de téléchargement, succès, erreurs). - Le format des logs doit inclure l'heure, le niveau du log et le message.
- Mettez en place un système de
-
Logique de téléchargement :
- Utilisez la bibliothèque
requests. Pour optimiser les connexions réseau, effectuez toutes vos requêtes à travers unerequests.Session. - Créez une fonction
download_and_savequi prend en charge le téléchargement et la sauvegarde d'une seule image. - Parcourez la liste d'URLs et appelez cette fonction pour chaque URL.
- Utilisez la bibliothèque
-
Gestion des erreurs :
- Votre script doit être robuste. Utilisez des blocs
try...exceptpour gérer les erreurs potentielles comme les erreurs réseau (requests.exceptions.RequestException) ou les problèmes d'écriture sur le disque (IOError). - Loguez les erreurs de manière explicite sans pour autant arrêter le script (une image qui ne se télécharge pas ne doit pas empêcher les autres de se télécharger).
- Votre script doit être robuste. Utilisez des blocs
-
Mesure de performance :
- Utilisez le module
time(par exemple,time.perf_counter()) pour calculer la durée totale d'exécution du processus de téléchargement. - Affichez le résultat à la fin du script.
- Utilisez le module
Partie 2 : téléchargement asynchrone
Maintenant que vous avez une version fonctionnelle, mais relativement lente, vous allez la réécrire en utilisant asyncio pour paralléliser les téléchargements et réduire le temps d'attente.
graph TD
A[Début] --> B{Lancement simultané des tâches};
subgraph "Exécution parallèle gérée par la boucle asyncio"
T1[Tâche 1: Get URL 1] --> W1[Attente Réseau 1] --> S1[Sauvegarde 1];
T2[Tâche 2: Get URL 2] --> W2[Attente Réseau 2] --> S2[Sauvegarde 2];
T3[...] --> W3[...] --> S3[...];
TN[Tâche N: Get URL N] --> WN[Attente Réseau N] --> SN[Sauvegarde N];
end
B --> T1;
B --> T2;
B --> T3;
B --> TN;
S1 --> G{Fin de toutes les tâches};
S2 --> G;
S3 --> G;
SN --> G;
G --> H[Fin du script];
Objectif
À partir du squelette de code fourni, compléter le script async_downloader.py qui accomplit la même tâche que le précédent, mais de manière asynchrone pour une performance maximale.
Cahier des charges
-
Structure asynchrone :
- Transformez votre logique en utilisant
asyncetawait. La fonction principale (main) et la fonction de téléchargement (download_and_save) doivent devenir des coroutines (async def). - Utilisez
asyncio.run(main())pour démarrer l'exécution.
- Transformez votre logique en utilisant
-
Logique de téléchargement asynchrone :
- Remplacez
requests.Sessionparaiohttp.ClientSession. - Dans votre fonction
download_and_save, utilisezsession.get()pour la requête etresponse.read()pour obtenir le contenu binaire de l'image. - Pour l'écriture des fichiers, remplacez
open()paraiofiles.open()pour ne pas bloquer la boucle événementielle.
- Remplacez
-
Exécution en parallèle :
- Dans votre fonction
main, créez une liste de "tâches" (une pour chaque image à télécharger). - Utilisez
asyncio.gather()pour lancer toutes les tâches en parallèle et attendre qu'elles soient toutes terminées.
- Dans votre fonction
-
Gestion des erreurs et logging :
- Adaptez la gestion des erreurs pour les exceptions spécifiques à
aiohttp(commeaiohttp.ClientError). - Conservez le système de
logging. Notez que dans un contexte asynchrone, les messages de log des différentes tâches peuvent s'entremêler, ce qui est normal.
- Adaptez la gestion des erreurs pour les exceptions spécifiques à
-
Mesure de performance :
- Mesurez à nouveau le temps d'exécution avec
time.perf_counter(). - Comparez ce temps avec celui obtenu dans la Partie 1. Qu'observez-vous ?
- Si vous avez le temps, essayez de télécharger un plus grand nombre d'images, provenant éventuellement de serveurs HTTP différents, pour voir l'impact de l'asynchronisme.
- Mesurez à nouveau le temps d'exécution avec
Question de réflexion
Pourquoi l'approche asynchrone est-elle efficace pour ce type de tâche (opérations d'entrée/sortie réseau) par rapport à l'approche synchrone ?