Puisque la programmation asynchrone est au goût du jour, on se mange des callbacks un peu partout. Et ça alourdit toujours le code. Chaque langage, lib ou framework a essayé de trouver des astuces pour rendre tout ça plus digeste, et on a vu la naissance des Futures, Deferred, Promises, coroutines, yield from et autres joyeusetés.
Prenons par exemple un script Twisted. Déjà, Twisted, c’est pas vraiment l’exemple de la syntaxe Weight Watcher, ou alors si, mais avant le début du régime.
# -*- coding: utf-8 -*- """ Télécharge des pages et affiche leur, de manière asynchrone """ import re # Ceci doit être pip installé import treq from twisted.internet.task import react from twisted.internet.defer import inlineCallbacks, returnValue # Soit on utilise la syntaxe 'inlineCallbacks', c'est à dire avec des yields # qui marquent les appels asynchrones. @inlineCallbacks def get_title(url): res = yield treq.get(url) # Ceci est asynchrone et non bloquant html = yield res.content() # Ça aussi try: val = re.search(r'', html.decode('utf8')).groups()[0] except: val = '' returnValue(val) # Soit on récupère un objet defer et on ajoute un callback manuellement def main(reactor): # Ceci est asynchrone et non bloquant defer = get_title('http://sametmax.com/quest-ce-quun-callback/') # Ceci arrive une fois que get_title est terminé def cb(title): print(title.upper() + '!') defer.addCallback(cb) # Pareil autre_defer = get_title('https://github.com/sametmax/django-quicky') def cb(title): print(title.upper() + '!!!') autre_defer.addCallback(cb) return defer react(main) |
D’une manière générale, je préfère la syntaxe à base de yields, même si elle oblige à se trimbaler le décorateur inlineCallbacks
partout, à parsemer sa fonction de yields et à utiliser returnValue
à la place de return
puisque le mot clé est interdit dans les générateurs en Python 2.7.
Mais bon, ça reste facile à lire. On sait que les lignes avec yield
, sont les appels bloquant qu’on demande à la boucle d’événements de traiter de manière asynchrone.
La syntaxe à base de callbacks est plus lourde, en revanche elle donne le contrôle sur la concurrence des callbacks puisqu’ils sont explicites au lieu d’être automatiquement ajoutés par magie. Elle parlera aussi plus aux dev Javascript qui ont l’habitude d’ajouter des callbacks manuellement.
Néanmoins, en JS, on a des fonctions anonymes plus flexibles, et on ferait donc plutôt une truc du genre :
get_title(url).then(function(title){ # faire un truc avec le résultat }) |
Et bien il se trouve qu’avec Python, bien qu’on ne le voit pas souvent, on peut avoir cette idée de la déclaration de son appel asynchrone juste au dessus de son callback, en utilisant des décorateurs.
En effet, les décorateurs ne sont que du sucre syntaxique :
@truc def bidule(): chose |
N’est en fait qu’un raccourci pour écrire :
def bidule(): chose bidule = truc(bidule) |
Du coup, on peut prendre n’importe quelle fonction, ou méthode, et l’utiliser comme décorateur :
@react def main(reactor): then = get_title('http://sametmax.com/quest-ce-quun-callback/').addCallback @then def cb(title): print(title.upper() + '!') then = get_title('https://github.com/sametmax/django-quicky').addCallback @then def cb(title): print(title.upper() + '!!!') return cb |
Et en jouant avec functools.partial
, on peut faire aussi des trucs rigolos.
Non pas que cette syntaxe soit le truc indispensable à connaître et à utiliser. Mais les gens n’y pensent jamais. On utilise pas assez les décorateurs.
Par exemple, combien de fois vous avez vu :
def main(): print('Doh') if __name__ == '__main__': main() |
Certaines libs, comme begin, font des décorateurs pour ça :
def main(func): if __name__ == '__main__': func() |
Et du coup, dans son prog:
@main def _(): print('Doh') |
Comme souvent, c’est le genre de feature qui peut être abusée, mais c’est parfois sympa de rapprocher une action juste au dessus de la fonction qui va être dans ce contexte.
J’espère ainsi vous avoir inspiré pour mettre un hack ou deux en production détournant complètement l’usage des décorateurs et ajoutant quelques gouttes de plus dans le vase de la sécurité de votre emploi, ou votre licenciement.