Jouons avec twisted
En travaillant sur topka, j'ai eu l'occasion d'utiliser twisted. Les tutoriaux de twisted sont bien fait, on a pleins d'exemples concrets, malgrés tout après avoir acquis un peu d'expérience avec le framework, j'ai eu envie d'écrire un petit article sur le sujet.
Note: cet article ne contient rien qui ne soit déjà dans la documentation de twisted
Utilisation des deferred
Quand on utilise twisted, il peut arriver qu'on se retrouve avec un résultat qui puisse être une valeur
ou bien un Deferred
. C'est le cas s'il est possible que le résultat soit disponible directement, ou bien qu'on
doivent attendre. On peut avoir ce genre de code:
import twisted.internet.defer as defer def myFunc(): def treatRet(): ... ret = functionThatProcess(...) if isinstance(ret, defer.Deferred): ret.addCallback(treatRet) else: ret = treatRet(ret) return ret
Avec l'expérience, je me suis rendu compte qu'il fallait mieux aborder la chose en considérant que le
résultat de functionThatProcess
était toujours un Deferred
. D'ailleurs on voit ici que les appelants
de myFunc
vont aussi tous devoir gérer les deux cas: retour sous forme de valeur ou de Deferred
.
C'est là que les appels defer.succeed
et defer.fail
arrivent à la rescousse. Petit exemple:
>>> import twisted.internet.defer as defer >>> d = defer.succeed('value') >>> def cb(v): ... print "value=", v ... return v ... >>> d.addCallback(cb) value= value <Deferred at 0x7fda5d44b9e0 current result: 'value'> >>>
On voit que sur un defer.succeed
, quand on ajoute une callbacks (ou une errback pour le defer.fail
), elle est appelée
instantanément. Ce qui permet de changer le code original en:
import twisted.internet.defer as defer def myFunc(): def treatRet(): ... ret = functionThatProcess(...) ret.addCallback(treatRet) return ret
Dans functionThatProcess
si la valeur retour est accessible directement, il suffira de
retourner un defer.succeed(value)
. Et cette fois, on accumule des callbacks sur le Deferred
retourné, et
soit toute la chaine sera exécutée au moment de la disponibilité du résultat, soit les callbacks sont exécutées
dés qu'elles sont rajoutées.
Chainer les deferred
J'ai aussi été confronté à un problème qui doit être assez courant: lors d'un appel sur un serveur
je renvoyais un Deferred
dont la callback était appelée quand on avait réussi à lancer un processus. Mais je
voulais que la chaîne de callbacks ne continue que dés que le processus en question avait créé un fichier (et mettre un timeout
sur cette attente), ce qui
imposait donc de faire une sorte d'attente dans la callback. Évidement si on fait ça de manière active, le reactor ne
marche plus et on a cassé le coté asynchrone. Une possibilité est de faire l'exécution dans un thread à part (reactor.callInThread
).
Mais le plus élégant, c'est encore de retourner un Deferred
dans la callback, de cette manière l'exécution des callbacks est interrompue
jusqu'à ce que le Deferred
retourné soit lui-même appelé:
from twisted.internet import defer, reactor def cb1(v): print "in callback 1 with value=", v d = defer.Deferred() reactor.callLater(1.0, d.callback, "modified") return d def cb2(v): print "in callback 2 with value=", v reactor.stop() delayed = defer.Deferred() delayed.addCallback(cb1).addCallback(cb2) reactor.callLater(2.0, delayed.callback, 'val') reactor.run()
Dans cet exemple la sortie est:
(attente de 2 secondes) in callback 1 with value= val (attente d'une seconde) in callback 2 with value= modified
On voit bien que l'exécution des callbacks est suspendue le temps que le Deferred
renvoyé
par cb1
soit résolu. Et d'ailleurs, c'est son résultat qui est passé à la callback
suivante.
Conclusion
La programmation avec twisted est quelque fois un peu compliqué, avec les callbacks, on peut terminer avec des programmes spaghetti. Néanmoins on peut quand même y trouver une certaine élégance. Je pense que quand je serais plus au point sur le sujet, il serait intéressant de faire un article sur les techniques de debuggage avec twisted.
Commentaires