Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Python
Code

Echanges E/S asynchrones avec Python 3

by
Difficulty:AdvancedLength:MediumLanguages:

French (Français) translation by Stéphane Esteve (you can also view the original English article)

Dans ce tutoriel, vous graviterez autour des fonctionnalités de communication asynchrone, développées à partir de la version 3.4 de Python, et augmentées depuis les versions 3.5 et 3.6.

Auparavant, Python contenait déjà quelques outils pour ce type de programmation. Le support des échanges asynchrones projette ce langage vers une exploitation optimisée, incluant des API de haut niveau et une base standard, permettant désormais d'unifier l'ensemble des solutions pré-existantes (Twisted, Gevent, Tornado, asyncore, etc.).

Notez qu'il est essentiel de comprendre que l'apprentissage des concepts asynchrones en Python sont loin d'être aisés, dû à son développement constant, son étendue davantage élargie et la prise en compte des évolutions des frameworks asynchrones déjà existants. Je me concentrerai sur les dernières innovations pour en faciliter légèrement sa compréhension.

Il y a tellement d'évolutions passionnantes sur le terrain des threads, des processus et des machines distantes. Et aussi de différences et de limitations liées aux spécificités d'une plateforme. Allons donc à sa découverte.

Les boucles d'événements raccordables

La boucle d'événement est le concept central des échanges asynchrones. Il y en a plusieurs au sein d'un programme. Chaque thread contient au moins une "event loop" active et offre les fonctions suivantes :

  • Déclarer, exécuter et annuler des appels différé (avec des clauses de timeouts).
  • Créer des passerelles client et serveur pour différentes sortes de communication.
  • Déclencher des sous-processus et les associer à des passerelles pour communiquer avec un programme externe.
  • Déléguer des exécutions de fonctions coûteuses vers un groupe de threads.

Un court exemple

Voici ici un petit exemple qui démarrent deux co-routines et exécute une fonction en différé. Il s'agit d'une démonstration de l'utilisation d'une event loop pour améliorer votre programme :

La classe AbstractEventLoop délivre le même principe à toute event loop. Plusieurs choses lui sont nécessaires pour fonctionner :

  • Hiérarchiser les fonctions et les co-routines pour toute exécution
  • Créer des fonctions anticipatrices et les tâches
  • Gérer des serveurs TCP
  • Manipuler des signaux (sur Unix)
  • Manipuler des pipes et des sous-processus

Voici les méthodes relatives à l'exécution et à l'arrêt des événements, ainsi qu'à la coordination des fonctions et des co-routines :

Raccorder à une nouvelle Event Loop

Asyncio est conçu pour supporter diverses implémentations d'event loop qui respectent le corps de son API. A la base, se trouve la classe EventLoopPolicy qui configure l'API asyncio et facilite le contrôle de chaque aspect de l'event loop. Voici ici un exemple d'une event loop personalisée appelée uvloop et basée sur libuv, qui est sensée être bien plus rapide que d'autres solutions (je ne l'ai pas évalué moi-même) :

C'est tout. Désormais, quelle que soit la fonction asyncio utilisée, c'est uvloop qui s'en chargera discrètement.

Co-routines, Futures et Tâches

Une co-routine est un terme chargé de sens. Tantôt, c'est une fonction qui s'exécute de manière asynchrone ; tantôt un objet qui a besoin d'être hiérarchisé. Vous la chargez en y ajoutant le mot-clé async devant sa définition.

Si vous l'exécutez directement, rien ne se passe. A la place un objet co-routine est renvoyé, et si aucune hiérarchisation n'est faite au cours de son exécution, voici l'avertissement obtenu :

Pour déclencher son exécution, vous avez besoin d'une event loop :

Il s'agit d'un séquençage direct. Vous pouvez aussi les lier les unes aux autres. Remarquez que vous devez inscrire await dès lors que vous invoquez les co-routines :

La class Future d'asyncio est similaire à la classe concurrent.future.Future. Ce n'est pas garanti au niveau thread et prend en charge les fonctionnalités suivantes :

  • ajouter ou retirer des callbacks terminés
  • annuler
  • formater les résultats et les exceptions

Voici la façon dont on peut utiliser un Future dans une event loop. La co-routine take_your_time() reçoit un Future et ajuste son résultat après avoir attendu une seconde.

La fonction ensure_future() planifie la co-routine et wait_until_complete() patiente jusqu'à la fin du Future. En fait, sous le capot, il ajoute un callback terminé au Future.

Tout ceci reste assez lourd. Asyncio délivre des tâches adaptées aux Futures et aux co-routines. Une Tâche est une sous-classe de Future, englobant une co-routine et que vous pouvez arrêter à tout moment.

La co-routine n'a plus qu'à adopter une fonction Future explicite et ajuste son résultat ou son exception. Même exemple qu'auparavant, mais cette fois, avec une tâche :

Passerelles, protocoles et flux

Une passerelle est une abstraction d'un canal de communication. Elle s'appuie toujours sur un protocole particulier. Asyncio contient un certain nombre d'implémentations clé-en-main pour le TCP, l'UDP, le SSL et aussi des sous-processus reliés.

Si vous connaissez déjà la programmation de réseaux basés sur les sockets, alors les passerelles et les protocoles n'ont plus aucun secret pour vous. Avec Asyncio, vous avez accès à la programmation standard de réseaux asynchrones. Reprenons l'infâme exemple du serveur d'écho et de son client (le "hello world" de la programmation réseau).

Premièrement, l'application client définit une classe appelée EchoClient , déclinaison de asyncio.Protocol. Elle conserve une event loop et un message qu'elle transmettra au serveur au cours de sa connexion.

Dans le callback connection_made(), elle inscrit son message à la passerelle. Dans la méthode data_received(), elle affiche la réponse du serveur, et par la méthode connection_lost() elle stoppe l'event loop. Dès qu'une instance d'EchoClient est définie dans la méthode create_connection(), le résultat obtenu est une co-routine que la boucle exécute tant qu'elle n'est pas terminée.

Le serveur reprend le même code, à l'exception qu'il s'exécute perpétuellement, attendant que des clients s'y connectent. Après avoir envoyé un signal contenant sa réponse, il coupe la connexion vers le client et il peut répondre au client suivant.

Une nouvelle instance de l'EchoServer est créée à chaque connexion. Ainsi, même si plusieurs clients se connectent en même temps, il n'y aura absolument aucun conflit grâce à l'attribut transport.

Voici ce qu'il affiche, suite à deux connexions clients :

Les flux nécessitent une API de haut-niveau, issues de co-routines, et produisent des abstraction de type Reader et Writer. Les protocoles et les passerelles sont transparents, ne nécessitant aucune déclaration de classe, ni aucun callbacks. Seuls sont attendus des événements de type connexion ou donnée.

Le client fait appel à la fonction open_connection() qui renvoit un objet reader et un objet writer, naturellement hérités. Pour fermer la connexion, elle détruit l'objet writer.

Le serveur est aussi davantage épuré.

Travailler avec des sous-processus

Asyncio renferme également des interactions possibles avec les sous-processus. Le programme ci-dessous déclenche un autre processus Python et exécute le code "import this". C'est une des pochette-surprises connu de Python, qui affiche le "Zen of Python". Constatez vous-même.

Le processus Python est déclenché par la co-routine zen() en utilisant la fonction create_subprocess_exec() et renvoie sa sortie standard à un autre conduit. Ainsi, il parcoure ce contenu ligne-à-ligne via await pour laisser aux autres processus ou co-routines la possibilité de s'exécuter si cette sortie n'est pas totalement conclue.

Remarquez que sous Windows, vous devez inscrire l'event loop au ProactorEventLoop , car la fonction SelectorEventLoop ne prend pas en charge les pipes.

Conclusion

N'hésitez pas à consulter ou acheter ce qu'il y a de disponible sur le sujet sur notre boutique, et contactez-nous si vous avez la moindre question. Nous sommes aussi attentifs à vos remarques, que vous pouvez laisser sur le formulaire ci-dessous.

Python Asyncio est un framework complet pour toute programmation asynchrone. Ses possibilités sont immenses et il pourvoit à la fois des API de bas et haut niveaux. Il est certes encore jeune et mal compris par la communauté.

Je suis persuadé qu'avec le temps, de bonnes conduites émergeront, et davantage d'exemples feront surface et rendront son utilisation plus poussée.

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.