Rendre une fonction asynchrone

Depuis quelques temps je joue un peu avec Twisted, et j’avoue que j’aime beaucoup, seulement c’est quand meme un truc assez balèse, et il peut arriver qu’on ait envie de rendre asynchrones quelques fonctions d’un programme, sans pour autant avoir Twisted pour dépendance.

C’est là qu’intervient toute la puissance de Python (non, je ne suis pas du tout de parti-pris, qu’on se le dise).

Le plus sympa pour rendre une fonction facilement asynchrone, ça serait d’avoir un décorateur qui fasse le travail pour nous, comme qui dit asynchrone dit callbacks, on va donc devoir faire un décorateur auquel on puisse passer des arguments. Un tel décorateur ressemble habituellement à ça:

class my_decorator(object):
    def __init__(self, argument):
        self.arguments = arguments
    def __call__(self, func):
        def wrapper(*args, **kwargs):
            # faire des trucs avec la fonction ici
        return wrapper

# usage
@my_decorator(deco_arg)
def my_func(arg):
    print "youpi"

Mais il faut que le code exécuté par notre décorateur le soit en arrière plan,  pour ça le mieux serait de l’exécuter dans un thread. Et si on faisait hériter notre décorateur de Thread pour que ce qu’il s’y passe se fasse en arrière plan? Voilà ce que ça donne:

from threading import Thread

class async(Thread):
    def __init__(self, callback, errback):
        super(async, self).__init__()
        self.callback = callback
        self.errback = errback

    def __call__(self, func):
        # à l'appel de la fonction, on récupère juste la fonction
        # et ses arguments, et on lance notre thread
        def wrapper(*args, **kwargs):
            self.func = func
            self.args = args
            self.kwargs = kwargs
            self.start()
        return wrapper

    def run(self):
        try:
            retval = self.func(*self.args, **self.kwargs)
            self.callback(retval)
        except Exception as err:
            self.errback(err)

Et c’est tout, magique non? Rendre une fonction asynchrone est devenu simple comme kikoo, il suffit de définir 2 fonctions, une pour le callback (appelée si la fonction est exécutée avec succès, elle reçoit la valeur renvoyée par la fonction en argument), et une pour l’errback (appelée si une exception est lancée lors de l’exécution de la fonction, elle reçoit l’exception en argument), et de décorer la fonction voulue.

Démonstration:

def my_callback(retval):
    print "CALLBACK:", retval
    # ici on peut faire des opérations supplémentaires
    # sur la valeur renvoyée par la fonction.
    # ce code est appelé dans le thread, il ne peut donc
    # pas bloquer notre thread principal

def my_errback(err):
    print "ERRBACK:", err
    # ici on peut re-lancer l'exception, ou simplement
    # l'ignorer. Ce code est également appelé dans le thread (logique)

@async(my_callback, my_errback):
def ma_fonction_reussie(n):
    sleep(n)
    return n

@async(my_callback, my_errback):
def ma_fonction_foireuse(n):
    sleep(n)
    raise AttributeError("Got %s, expected 'foo'" % n)

ma_fonction_reussie(5)
ma_fonction_foireuse(7)

# histoire de passer le temps pendant ce temps là...
for i in range(10):
    print "MAIN:", i
    sleep(1)

Résultat:

MAIN: 0
MAIN: 1
MAIN: 2
MAIN: 3
MAIN: 4
MAIN: 5
CALLBACK: 5
MAIN: 6
MAIN: 7
ERRBACK: Got 7, expected 'foo'
MAIN: 8
MAIN: 9

Code complet avec l’exemple

Publicités

Une réflexion sur “Rendre une fonction asynchrone

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s