Requêtes HTTP asynchrones dans Python avec aiohttp et asyncio
Temps de lecture: 5 minutes
De plus en plus, le code asynchrone est devenu un pilier du développement Python. Comme il fait partie asyncio de la librairie standard et que de nombreux packages tiers fournissent des fonctionnalités compatibles avec lui, ce paradigme n'est pas prêt de disparaître.
Voyons comment utiliser la librairie aiohttp pour tirer parti de cette fonctionnalité et créer des requêtes HTTP asynchrones, soit l'un des cas d'utilisation les plus courants pour le code non bloquant.
Qu'est-ce qu'un code non bloquant ?
Vous pouvez entendre des termes tels que « asynchrone », « non bloquant » ou « simultané » et vous retrouver un peu confus quant à leur signification. Selon ce tutoriel beaucoup plus détaillé, deux des propriétés principales sont les suivantes :
- Les routines asynchrones peuvent faire « pause » tout en attendant leur résultat final pour permettre à d'autres routines de s'exécuter en même temps.
- Le code asynchrone, via le mécanisme ci-dessus, facilite l'exécution simultanée. Pour simplifier, le code asynchrone donne l'aspect de la simultanéité.
Le code asynchrone est donc un code qui peut se mettre en pause en attendant un résultat, afin de permettre à d'autres codes de s'exécuter entre-temps. Il ne « bloque » pas l'exécution d'un autre code et nous pouvons donc l'appeler code « non bloquant ».
Pour ce faire, la librairie asyncio offre de nombreux outils pour les développeurs Python pour cette tâche, et aiohttp propose une fonctionnalité encore plus spécifique pour travailler avec des requêtes HTTP. Les requêtes HTTP sont un exemple classique d'élément bien adapté à l'asynchrone, car elles impliquent l'attente d'une réponse d'un serveur, attente pendant laquelle il serait pratique et efficace d'exécuter d'autres codes.
Configurer
Assurez-vous de configurer votre environnement Python avant de commencer. Suivez ce guide dans la section virtualenv si vous avez besoin d'aide. Faire en sorte que tout fonctionne - en particulier en ce qui concerne les environnements virtuels - est important pour isoler vos dépendances si plusieurs projets s'exécutent sur la même machine. Vous aurez besoin d'au moins Python 3.7 ou une version ultérieure pour exécuter le code de ce post.
Maintenant que votre environnement est configuré, vous devez installer des librairies tierces. Nous allons utiliser aiohttp pour effectuer des requêtes asynchrones et la librairie de requêtes pour effectuer des requêtes HTTP synchrones régulières afin de les comparer ultérieurement. Installez les deux à l'aide de la commande suivante après avoir activé votre environnement virtuel :
Ensuite, vous pouvez passer à l'écriture du code.
Effectuer une requête HTTP avec aiohttp
Commençons par effectuer une seule requête GET
à l'aide d'aiohttp, afin de démontrer le fonctionnement des mots clés async
et await
. Nous allons utiliser l'API Pokemon comme exemple. Commençons donc par essayer d'obtenir les données associées à Mew, le légendaire 151e Pokémon.
Exécutez le code Python suivant et le nom « mew » devrait s'afficher sur le terminal :
Dans ce code, nous créons une coroutine appelée main
, que nous utilisons avec la boucle d'événements asyncio. Ici, nous ouvrons une session du client aiohttp, un objet unique qui peut être utilisé pour un grand nombre de requêtes individuelles et, par défaut, peut établir des connexions avec jusqu'à 100 serveurs différents à la fois. Au cours de cette session, nous faisons une requête à l'API Pokemon, puis nous attendons une réponse.
Pour faire simple, ce mot clé async
(asynchrone) indique à l'interpréteur Python que la coroutine que nous définissons doit être exécutée de manière asynchrone avec une boucle d'événements. Le mot clé await
(attente) renvoie le contrôle à la boucle d'événements, en suspendant l'exécution de la coroutine environnante et en laissant la boucle d'événements exécuter d'autres opérations jusqu'à ce que le résultat « attendu » soit renvoyé.
Effectuer un grand nombre de requêtes
Effectuer une seule requête HTTP asynchrone est très utile, car nous pouvons laisser la boucle d'événements travailler sur d'autres tâches au lieu de bloquer l'intégralité du thread en attendant une réponse. Mais l'efficacité de cette fonctionnalité s'exprime réellement lorsque vous essayez de faire un plus grand nombre de requêtes. Faisons la démonstration en effectuant la même requête qu'auparavant, mais pour les 150 Pokémon originaux.
Prenons le code de requête précédent et plaçons-le dans une boucle, en mettant à jour les données de Pokemon demandées et en utilisant await
pour chaque requête :
Cette fois-ci, nous mesurons également le temps nécessaire à l'exécution de l'ensemble du processus. Si vous exécutez ce code dans votre shell Python, vous devriez voir quelque chose comme ce qui suit sur votre terminal :
Un délai de 8 secondes, c'est plutôt bien pour 150 requêtes, mais nous n'avons rien à quoi le comparer. Essayons d'effectuer la même opération de manière synchrone à l'aide de la librairie de requêtes.
Comparer la vitesse avec les requêtes synchrones
Les requêtes ont été conçues comme une librairie HTTP « pour les êtres humains ». Elle dispose d'une API très belle et simple. Je le recommande vivement pour tous les projets où la vitesse n'est pas forcément d'une importance capitale par rapport à la convivialité de développement et à la facilité du code à suivre.
Pour afficher les 150 premiers Pokémon comme précédemment, mais à l'aide de la librairie de requêtes, exécutez le code suivant :
Vous devriez voir le même résultat avec une exécution différente :
Avec près de 29 secondes, cette opération est beaucoup plus lente qu'avec le code précédent. Pour chaque requête consécutive, nous devons attendre la fin de l'étape précédente avant même de commencer le processus. Cela prend beaucoup plus de temps, car ce code attend que 150 requêtes se terminent de manière séquentielle.
Utiliser asyncio pour des performances améliorées
Un délai de 8 secondes contre 29 secondes représente donc un énorme bond en avant en matière de performance, mais nous pouvons encore mieux faire en utilisant les outils fournis par asyncio
. Dans l'exemple d'origine, nous utilisons await
après chaque requête HTTP, ce qui n'est pas tout à fait idéal. C'est tout de même plus rapide que l'exemple des requêtes, car nous exécutons tout dans des routines communes. Cependant, nous pouvons exécuter toutes ces requêtes « simultanément » en tant que tâches asyncio, puis vérifier les résultats à la fin à l'aide des asyncio.ensure_future
et asyncio.gather
.
Si le code qui effectue la requête est dissocié de sa propre fonction de coroutine, nous pouvons créer une liste de tâches composée de fonctions futures pour chaque requête. Nous pouvons ensuite décompresser cette liste vers un appel gather qui exécute les requêtes toutes ensemble. Lorsque nous utilisons la fonction await
pour cet appel sur asyncio.gather
, nous récupérons un itérable pour toutes les fonctions futures qui ont été passées, en conservant leur ordre dans la liste. De cette façon, nous n'attendons qu'une seule fois.
Pour voir ce qui se passe lorsque nous implémentons cette fonctionnalité, exécutez le code suivant :
Notre temps de traitement est ainsi réduit à seulement 1,53 seconde pour 150 requêtes HTTP ! C'est une amélioration considérable par rapport à notre exemple initial avec async/await. Cet exemple n'est pas totalement non bloquant. Par conséquent, la durée totale d'exécution des 150 requêtes sera à peu près égale à la durée d'exécution de la requête la plus longue. Les chiffres exacts varient en fonction de votre connexion Internet.
Réflexions finales
Comme vous pouvez le voir, l'utilisation de librairies comme aiohttp permet de repenser la façon dont vous effectuez des requêtes HTTP, mais aussi d'augmenter considérablement les performances de votre code et de vous faire gagner beaucoup de temps lorsque vous effectuez un grand nombre de requêtes. Par défaut, elles sont un peu plus denses que les librairies synchrones telles que les requêtes, mais c'est voulu, car les développeurs pensent d'abord aux performances.
Dans ce tutoriel, nous n'avons fait qu'effleurer la surface de ce que vous pouvez faire avec aiohttp et asyncio, mais j'espère que cela vous a permis de vous lancer un peu plus facilement dans le monde du Python asynchrone.
J'ai hâte de découvrir ce que vous construisez. N'hésitez pas à me contacter et à partager vos expériences ou à poser des questions.
- Adresse e-mail : sagnew@twilio.com
- Twitter : @Sagnewshreds
- Github : Sagnew
- Twitch (diffusion de code en direct) :Sagnewshreds
Articles associés
Ressources connexes
Twilio Docs
Des API aux SDK en passant par les exemples d'applications
Documentation de référence sur l'API, SDK, bibliothèques d'assistance, démarrages rapides et didacticiels pour votre langage et votre plateforme.
Centre de ressources
Les derniers ebooks, rapports de l'industrie et webinaires
Apprenez des experts en engagement client pour améliorer votre propre communication.
Ahoy
Le hub de la communauté des développeurs de Twilio
Meilleures pratiques, exemples de code et inspiration pour créer des expériences de communication et d'engagement numérique.