Vous vous souvenez du mots clefs yield? C’est celui la que nous utilisions dans les générateurs pour produire des résultats sans interrompre le déroulement du contexte d'exécution… c'était assez magique… à bien sachez qu’il est aussi possible de lui envoyer des données en contre partie…. dur à imaginer…. effectivement…Prenons un exemple:
import unittest import re def producteur(file): print("PROD: begin producer") with open(file,'r') as lfile: continu=True print("PROD: ouverture du fichier {0} avec boucle init en {1}".format(file,continu)) while(continu): print("PROD: on est dans la boucle") chaine=lfile.readline() print("PROD: dans la boucle, la chaine lu est {0}".format(chaine)) continu= (yield chaine) print("PROD: on nous repond si on peut continuer {0}".format(continu)) print("PROD: on est sorti de la boucle et on se met en attente de cloture") yield class TestCoroutine(unittest.TestCase): def testCouroutineSimpel(self): count=3 gen=producteur('cor.txt') #help(gen) print("TEST: on initialise le contexte du test avec un nombre de recherche limité a {0} et un generateur {1}".format(count,gen)) chaine=gen.__next__() while(True): print("TEST: la chaine vaut {0}, le nombre d'element a reconnaitre {1}".format(chaine,count)) if(re.search(r".*mas",chaine) is not None) : print("TEST: on a reconnu un element on va decrementer") count-=1 chaine=gen.send(True) if(count==0): print("TEST: on a suffisament decrementé, on envoye un message pour mettre fin a l'iteration ") gen.send(False) break print("TEST: on cloture le generateur") gen.close() self.assertEqual(0,count) if __name__ == '__main__': unittest.main(argv=['first-arg-is-ignored'], exit=False)
Cet exemple est simpliste mais il illustre bien le mécanisme mis en oeuvre et le principe de la coroutine dans laquelle au travers de la méthode send, il va être possible d’injecter des données à la fonction lui permettant de conserver son contexte d'exécution mais d'être adapté à la circonstance (résultant de l’execution du renvoie de la valeur par le yield.)
En fait il s’agit ici d’un sorte d’artifice permettant d'éviter de découper une fonction en deux car l’un de ses paramètres est dépendant d’un résultat externe qui devrait être calculé dans l’entre temps.
TEST: on initialise le contexte du test avec un nombre de recherche limité a 3 et un generateur <generator object producteur at 0x03030E70> PROD: begin producer PROD: ouverture du fichier cor.txt avec boucle init en True PROD: on est dans la boucle PROD: dans la boucle, la chaine lu est thomas1 TEST: la chaine vaut thomas1 , le nombre d'element a reconnaitre 3 TEST: on a reconnu un element on va decrementer PROD: on nous repond si on peut continuer True PROD: on est dans la boucle PROD: dans la boucle, la chaine lu est thomas2 TEST: la chaine vaut thomas2 , le nombre d'element a reconnaitre 2 TEST: on a reconnu un element on va decrementer PROD: on nous repond si on peut continuer True PROD: on est dans la boucle PROD: dans la boucle, la chaine lu est thomas3 TEST: la chaine vaut thomas3 , le nombre d'element a reconnaitre 1 TEST: on a reconnu un element on va decrementer PROD: on nous repond si on peut continuer True PROD: on est dans la boucle PROD: dans la boucle, la chaine lu est thomas4 TEST: on a suffisament decrementé, on envoye un message pour mettre fin a l'iteration PROD: on nous repond si on peut continuer False PROD: on est sorti de la boucle et on se met en attente de cloture TEST: on cloture le generateur ---------------------------------------------------------------------- Ran 1 test in 0.009s OK
Ainsi dans l’exemple, le générateur va s'exécuter au fil des sollicitations produite dans le test. On initialise le générateur, celui ci va s'exécuter jusqu’au premier yield sans l'exécuter. C’est l’instruction __init__ qui va alors demander au générateur de renvoyer le résultat obtenu sans pour autant avancer dans l'exécution, pour cela, il va attendre un send qui lui permettra d’assigner la valeur à la variable continu et la il va poursuivre son exécution jusqu’à boucler jusqu’au yield suivant. La il donnera alors la main à la boucle principale qui va elle aussi boucler jusqu’au send suivant.
La boucle est bouclé, il reste alors à prévoir un mécanisme de sortie en comptant ici le nombre de fois que l’on rencontre une chaîne de caractères. Alors à ce moment la on arrête la boucle principale via un break ainsi que la boucle du générateur. On n’oubliera pas alors de clôturer complètement le générateur via la méthode close.
Ici on a le détail du fonctionnement du générateur si il est nécessaire de lui passer des paramètres au cours de son exécution afin qu’il puisse s’adapter dynamiquement. Bien sur dans le cas contraire l’exemple fourni dans l’article [generateur] sera préférable.
Accessoirement, vous aurez noté l'utilisation des librairies unittest et re pour réaliser cet article. L'idée n'est pas de rentrer pas dans le détail de ces librairies aujourd'hui puisqu'elles serviront a l'avenir très souvent et que nous les parleront mais pour en donner deux mots, la première est une librairie permettant la mise en oeuvre de tests unitaires en python, c'est donc un indispensable et la second est une librairie permettant la gestion d'expression régulière et leur reconnaissance/extraction dans des données. (accessoirement on a aussi vu une approche pour accéder a des fichiers aussi).
Enfin bref, pour conclure, les coroutines ne sont pas triviales a manipuler mais elles permettent de simuler des exécutions concurrentes assez efficacement. Pour aller plus loin, je vous invite a consulter la référence sur le sujet [pep-342]
Références
[pep-342] https://www.python.org/dev/peps/pep-0342/[generateur] https://un-est-tout-et-tout-est-un.blogspot.com/2019/03/python-listes-et-generateurs.html
[decorateur] https://un-est-tout-et-tout-est-un.blogspot.com/2019/03/python-les-decorateurs.html
[uniitest] https://medium.com/@vladbezden/using-python-unittest-in-ipython-or-jupyter-732448724e31
Aucun commentaire:
Enregistrer un commentaire