Introduction
Vous débutez en programmation Python ? Python vous intéresse ? Vous avez certainement déjà entendu parler des “décorateurs”. C’est une fonctionnalité très utilisée dans le monde Python. Ils permettent d’écrire du code concis, lisible et non-répétitif.
Concrètement, un décorateur est un callable qui prend pour argument un callable et qui retourne une copie de ce même callable en le “décorant”, c’est-à-dire en effectuant un pré-traitement et/ou un post-traitement sur celui-ci. Dans le jargon, on parle souvent de wrapping (on enveloppe, on saupoudre, on apporte un “plus”, on décore).
Si vous connaissez un peu le framework web Django,
vous avez peut-être déjà manipulé des décorateurs, notamment pour
la gestion de l’authentification.
Pour protéger les vues, il suffit de les décorer avec les décorateurs
@login_required
ou @permission_required
.
Un exemple :
@login_required
def protected_view(request):
...
Dans cet exemple, la vue protected_view
est automatiquement protégée. Si vous
n’êtes pas un utilisateur enregistré et connecté, vous serez redirigé vers le
formulaire d’authentification, puisque le décorateur @login_required
a ajouté
son grain de sel pour que nous écrivions le moins de code possible.
Le micro-framework web Bottle utilise essentiellement cette fonctionnalité du langage.
Un exemple :
@route('/hello/:names')
@view('hello')
def hello(names):
names = names.split(',')
return dict(title='Hello World', names=names)
Ici, le décorateur @route
se charge de définir une route pour cette vue et
le décorateur @view
se charge de charger le template hello.tpl
et d’y
injecter les variables retournées dans son contexte. Comme vous pouvez le
constater, nous pouvons exécuter plusieurs décorateurs sur une même
fonction car nous pouvons les “chaîner”.
Il existe deux types de syntaxes.
La syntaxe arobase @
:
@dec2
@dec1
def func(arg1, arg2):
pass
Et la syntaxe classique :
def func(arg1, arg2):
pass
func = dec2(dec1(func))
Les deux exemples de code ci-dessus sont équivalents. On utilise, généralement,
la syntaxe @
lorsque nous souhaitons “figer” le comportement de la fonction
(lors de son appel, ses décorateurs seront alors systématiquement exécutés) et
la syntaxe classique si nous n’avons pas accés à sa définition ou lorsqu’on
souhaite modifier son comportement à un endroit précis dans le code.
Les décorateurs peuvent prendre des arguments requis ou optionnels, en plus
de la fonction à décorer. On peut alors modifier dynamiquement leur
comportement. C’est le cas dans l’exemple du framework web Bottle avec les
décorateurs @route
et @view
. C’est aussi le cas pour les décorateurs
@login_required
ou @permission_required
de Django.
Un exemple avec @permission_required
:
@permission_required('polls.can_vote', login_url='/loginpage/')
def my_view(request):
...
Non seulement de prendre pour argument la fonction à décorer, ce décorateur prend aussi un argument requis (la permission) et un argument optionnel (l’URL du formulaire d’authentification en cas d’échec).
Maintenant que nous connaissons un peu mieux les décorateurs, passons à la
pratique. Nous allons, tout d’abord, implémenter un décorateur “simple” ne
prenant pas d’argument. Nous aborderons ensuite l’implémentation de
décorateurs plus élaborés, capables d’accepter des arguments (requis et/ou
optionnels). Nous ferons ensuite connaissance avec functools.wraps
(inclus
dans Python). Et nous terminerons avec un exemple concret.
Décorateur simple
Commençons par un premier exemple :
# -*- coding: utf-8 -*-
# Notre décorateur
def decorate(func):
print u"Je suis dans la fonction 'decorate' et je décore '%s'." % func.__name__
print u"Exécution de la fonction '%s'." % func.__name__
return func
# Notre fonction décorée
@decorate
def foobar(*args):
print ", ".join(args)
# Appel de la fonction
foobar("A", "B", "C", "D")
Exécutons le code :
Je suis dans la fonction 'decorate' et je décore 'foobar'.
Exécution de la fonction 'foobar'.
A, B, C, D
Notre fonction a bien été décorée. Mais dans cet exemple, nous avons pas accès aux arguments de la fonction décorée. On affiche simplement une chaîne de caractères pour s’informer de l’éxecution du décorateur.
Modifions notre code pour pouvoir accéder aux arguments de la fonction décorée :
# -*- coding: utf-8 -*-
# Notre décorateur
def decorate(func):
print u"Je suis dans la fonction 'decorate' et je décore '%s.'" % func.__name__
def wrapper(*args, **kwargs):
print u"Je suis dans la fonction 'wrapper' qui accède aux arguments de '%s'." % func.__name__
a = list(args)
a.reverse()
print u"Je t'en donne la preuve, je peux les inverser : %s." % ', '.join(a)
print u"Exécution de la fonction '%s'." % func.__name__
return func(*args, **kwargs)
return wrapper
# Notre fonction décorée
@decorate
def foobar(*args):
print ", ".join(args)
# Appel de la fonction
foobar("A", "B", "C", "D")
Exécutons-le :
Je suis dans la fonction 'decorate' et je décore 'foobar.'
Je suis dans la fonction 'wrapper' qui accède aux arguments de 'foobar'.
Je t'en donne la preuve, je peux les inverser : D, C, B, A.
Exécution de la fonction 'foobar'.
A, B, C, D
Nous avons créé un wrapper, qui n’est autre qu’une fonction possédant la même signature (les mêmes arguments) que la fonction à décorer et qui retourne la fonction décorée, avec ses arguments. Le wrapper est lui-même retourné par le décorateur. C’est un système à la poupées Russes.
Dans cet exemple, nous avons seulement effectué un pré-traitement. Mais il est tout à fait possible, très simplement, d’effectuer un post-traitement sur la fonction à décorer :
# -*- coding: utf-8 -*-
# Notre décorateur
def decorate(func):
print u"Je suis dans la fonction 'decorate' et je décore '%s.'" % func.__name__
def wrapper(*args, **kwargs):
print u"Je suis dans la fonction 'wrapper' qui accède aux arguments de '%s'." % func.__name__
a = list(args)
a.reverse()
print u"J'en donne la preuve, je peux les inverser : %s." % ', '.join(a)
print u"Exécution de la fonction '%s'." % func.__name__
response = func(*args)
print u"Je peux effectuer, ici, un post-traitement."
return response
return wrapper
# Notre fonction décorée
@decorate
def foobar(*args):
print ", ".join(args)
# Appel de la fonction
foobar("A", "B", "C", "D")
Exécutons le code :
Je suis dans la fonction 'decorate' et je décore 'foobar.'
Je suis dans la fonction 'wrapper' qui accède aux arguments de 'foobar'.
Je t'en donne la preuve, je peux les inverser : D, C, B, A.
Exécution de la fonction 'foobar'.
A, B, C, D
Je peux effectuer, ici, un post-traitement.
Pour simplifier au maximum :
def decorate(func):
def wrapper(*args, **kwargs):
# Pré-traitement
response = func(*args, **kwargs)
# Post-traitement
return response
return wrapper
Ici, je récupère le retour de la fonction dans response
et je retourne
ensuite la valeur après le post-traitement. Mais si vous n’avez pas besoin
d’accéder aux données retournées par la fonction à décorer, ce qui était un
peu notre cas, il suffit juste d’appeller la fonction :
def decorate(func):
def wrapper(*args, **kwargs):
# Pré-traitement
func(*args, **kwargs)
# Post-traitement
return wrapper
Cela dépend vraiment de votre implémentation.
C’est un peu plus clair ? Si vous avez compris les exemples ci-dessus et ce bout de code, vous avez tout compris.
J’entends certains penser fortement… Si un décorateur prend pour argument un
callable, on doit pouvoir lui passer d’autres arguments, non ? Si, par
exemple, on souhaite modifier son comportement dynamiquement ? Comme pour le
décorateur @permission_required
de Django, qui prend pour arguments une
permission donnée et, éventuellement, l’URL de la page de connexion pour
rediriger automatiquement l’utilisateur en cas d’échec :
@permission_required('polls.can_vote', login_url='/loginpage/')
def my_view(request):
...
Ou bien, comme avec les vues de Bottle et leurs décorateurs @route
et @view
:
@route('/hello/:names')
@view('hello')
def hello(names):
names = names.split(',')
return dict(title='Hello World', names=names)
Bien vu. Nous avons abordé l’implémentation d’un décorateur simple qui ne prend pas d’argument, excepté le callable à décorer. On ne peut donc pas modifier dynamiquement son comportement en précisant diverses options. Voyons comment implémenter un décorateur capable d’accepter des arguments.
Décorateur avec arguments
Pour implémenter un décorateur dynamique capable d’accépter des arguments (ou “options”), nous devons ajouter un niveau supplémentaire : un décorateur qui prend pour arguments les arguments du décorateur de la fonction à décorer. Vous suivez toujours ? Pour un décorateur simple, nous avons deux niveaux de fonctions (le décorateur et le wrapper). Pour un décorateur avec arguments, nous en avons trois (le décorateur du décorateur, le décorateur et le wrapper).
Si on simplifie au maxium :
def decorate(arg1, arg2, arg3):
def decorated(func):
def wrapper(*args, **kwargs):
# Pré-traitement
response = func(*args, **kwargs)
# Post-traitement
return response
return wrapper
return decorated
Prenons un exemple :
# -*- coding: utf-8 -*-
def decorate(arg1, arg2, arg3):
print u'Je suis dans la fonction "decorate".'
def decorated(func):
print u'Je suis dans la fonction "decorated".'
def wrapper(*args, **kwargs):
print u'Je suis dans la fonction "wrapper".'
print u"Les arguments du décorateurs sont : %s, %s, %s." % (arg1, arg2, arg3)
print u"Pré-traitement."
print u"Exécution de la fonction %s." % func.__name__
response = func(*args, **kwargs)
print u"Post-traitement."
return response
return wrapper
return decorated
@decorate("Arg 1", "Arg 2", "Arg 3")
def foobar():
print u"Je suis foobar, je vous reçois 5 sur 5."
foobar()
Si on exécute le code :
Je suis dans la fonction "decorate".
Je suis dans la fonction "decorated".
Je suis dans la fonction "wrapper".
Les arguments du décorateurs sont : Arg 1, Arg 2, Arg 3.
Pré-traitement.
Exécution de la fonction foobar.
Je suis foobar, je vous reçois 5 sur 5.
Post-traitement.
Les arguments passés au décorateur ont bien été pris en compte.
Nous avons implémenté un décorateur avec arguments obligatoires (ou requis). Pour passer des arguments optionnels :
def decorate(arg1='default', arg2=None, arg3=None):
def decorated(func):
def wrapper(*args, **kwargs):
# Pré-traitement
response = func(*args, **kwargs)
# Post-traitement
return response
return wrapper
return decorated
@decorate(arg1='my value')
def foobar():
pass
Attention. Comme tous les arguments de ce décorateur sont optionnels, si on ne souhaite pas les modifier, on serait tenté d’appeller le décorateur comme un décorateur “simple” :
@decorate
def foobar():
pass
Si on exécute ce code :
Traceback (most recent call last):
File "exemple.py", line 22, in <module>
foobar()
TypeError: decorated() takes exactly 1 argument (0 given)
Catastrophe ! Python vous demandera quand même de founir un argument, qu’il considère comme “requis” (même si tous les arguments sont optionnels). Dans ce cas, la bonne syntaxe est d’utiliser les parenthèses d’appel de fonction :
@decorate()
def foobar():
pass
Ce n’est pas très sexy, source d’erreur, mais ça fonctionne. Nous aborderons plus loin comment contourner ce “problème”.
Un autre exemple, avec un argument obligatoire et des arguments optionnels :
def decorate(arg1, arg2='default value', arg3=None):
def decorated(func):
def wrapper(*args, **kwargs):
# Pré-traitement
response = func(*args, **kwargs)
# Post-traitement
return response
return wrapper
return decorated
@decorate('required value')
def foo():
pass
@decorate('required value', arg3='my value')
def bar():
pass
Retenez surtout les trois niveaux de fonctions (la fonction qui prend pour arguments les arguments du décorateur, le décorateur et le wrapper) et les parenthèses d’appel de fonction pour un décorateur avec arguments optionnels appelé sans argument.
Préservation de la fonction décorée
Jusque là, on s’est intéressé au décorateur, pas vraiment à la fonction décorée.
Penchons-nous de plus près sur la fonction décorée :
# -*- coding: utf-8 -*-
def logged(func):
def wrapper(*args, **kwargs):
"""Je suis la documentation de logged."""
print u"Exécution de %s." % func.__name__
return func(*args, **kwargs)
return wrapper
@logged
def foobar(arg):
"""Je suis la documentation de foobar."""
print arg
foobar("Yeah!")
print foobar.__name__
print foobar.__doc__
Si on exécute ce bout de code :
Exécution de foobar.
Yeah!
_logged
Je suis la documentation de logged.
Mince ! On appelle la fonction foobar
et c’est pourtant le nom et la
documentation du décorateur logged
que nous renvoie foobar.__name__
et
foobar.__doc__
. Ça, c’est bien le genre de truc à donner des migraines. Si
on y réfléchie bien, c’est un comportement logique puisque la fonction
retournée par le décorateur est une fonction de substitution, pas notre
vraie foobar
.
Heureusement, si vous utilisez Python 2.5 ou supérieure, functools.wraps
vient à notre rescousse. Ce décorateur va préserver notre fonction décorée avec
élégance et délicatesse. Pour ce faire, nous devons juste décorer le wrapper
avec ce décorateur, en lui passant comme argument la fonction à décorer.
Ensuite, il se charge du reste.
Un exemple :
# -*- coding: utf-8 -*-
import functools
def logged(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
"""Je suis la documentation de logged."""
print u"Exécution de %s." % func.__name__
return func(*args, **kwargs)
return wrapper
def foobar(arg):
"""Je suis la documentation de foobar."""
print arg
foobar = logged(foobar)
foobar("Yeah!")
print foobar.__name__
print foobar.__doc__
Si on exécute ce code :
Exécution de foobar.
Yeah!
foobar
Je suis la documentation de foobar.
Hallelujah ! Ça marche.
Si vous n’utilisez pas Python 2.5 ou supérieur mais si vous utilisez Django et
Python 2.4, vous pouvez quand même bénéficier de cette fonctionnalité via
django.utils.functional.wraps
.
Décorateurs avec ou sans arguments
Nous avons vu, plus haut, comment implémenter un décorateur avec arguments obligatoires ou optionnels. Nous nous sommes aperçu que si nous appellons un décorateur ne prenant que des arguments optionnels sans argument, comme un décorateur “simple”, Python renvoie une erreur. En ajoutant les parenthèses d’appel de fonction, on contourne le problème.
Exemple :
# -*- coding: utf-8 -*-
def decorate(arg1='default', arg2=None, arg3=None):
def decorated(func):
def wrapper(*args, **kwargs):
# Pré-traitement
response = func(*args, **kwargs)
# Post-traitement
return response
return wrapper
return decorated
# MAUVAIS
@decorate
def foobar():
pass
# BON
@decorate()
def foobar():
pass
Malheureusement, il n’existe pas de solution générique. Pas de solution idéale qui nous permettrait d’implémenter des décorateurs capables d’être appelés avec ou sans arguments, sans parenthèses d’appel de fonction. Un décorateur ne prend pas d’argument ? Pas de paranthèses. Un décorateur prend des arguments optionnels, même avec des valeurs par défaut ? Lorsqu’il est appelé seul, les parenthèses s’imposent. Pour un décorateur avec des arguments obligatoires, on ne se pose même pas la question puisque nous sommes “obligé” de lui donner des arguments.
Cependant, si il n’existe pas de solution “générique”, nous pouvons quand
même modifier notre implémentation pour éviter l’emploi des parenthèses.
Nous devons alors donner notre fonction à décorer comme premier argument
au décorateur de notre décorateur. On l’initialisera à None
. Si nous
fournissons des arguments optionnels, la valeur de notre fonction sera None
,
on retournera alors une fonction prenant pour argument notre fonction à décorer
et qui retournera notre décorateur. Si la valeur de notre fonction n’est pas
None
, cela sous-entend que nous appelons notre décorateur sans argument, ni
parenthèses.
Pour mieux comprendre, modifions notre exemple :
# -*- coding: utf-8 -*-
def decorate(func=None, arg1=None, arg2=None, arg3=None):
print u'Je suis dans la fonction "decorate".'
def decorated(func):
print u'Je suis dans la fonction "decorated".'
def wrapper(*args, **kwargs):
print u'Je suis dans la fonction "wrapper".'
print u"Les arguments du décorateurs sont : %s, %s, %s." % (arg1, arg2, arg3)
print u"Pré-traitement."
print u"Exécution de la fonction %s." % func.__name__
response = func(*args, **kwargs)
print u"Post-traitement."
return response
return wrapper
if func is None:
# Le décorateur est appellé avec des arguments
def decorator(func):
return decorated(func)
return decorator
# Le décorateur est appellé sans arguments
return decorated(func)
@decorate
def foobar():
print u"Je suis foobar, je vous reçois 5 sur 5."
foobar()
Si on exécute ce code :
Je suis dans la fonction "decorate".
Je suis dans la fonction "decorated".
Je suis dans la fonction "wrapper".
Les arguments du décorateurs sont : None, None, None.
Pré-traitement.
Exécution de la fonction foobar.
Je suis foobar, je vous reçois 5 sur 5.
Post-traitement.
Ça marche ! Nous n’avons plus besoin d’ajouter les parenthèses à notre décorateur quand il est appelé sans argument. Mais, en contre-partie, cela nous contraint à lui donner uniquement des arguments nommés :
# BON
@decorate(arg1="Python", arg2="Haskell")
def foobar():
print u"Je suis foobar, je vous reçois 5 sur 5."
# MAUVAIS
@decorate("Python", "Haskell")
def foobar():
print u"Je suis foobar, je vous reçois 5 sur 5."
Si on exécute le second exemple :
Je suis dans la fonction "decorate".
Je suis dans la fonction "decorated".
Je suis dans la fonction "wrapper".
Les arguments du décorateurs sont : Haskell, None, None.
Pré-traitement.
Traceback (most recent call last):
File "exemple.py", line 24, in <module>
@decorate("Python", "Haskell")
File "exemple.py", line 11, in wrapper
print u"Exécution de la fonction %s." % func.__name__
AttributeError: 'str' object has no attribute '__name__'
Ça coince. Notre chaîne de caractères “Python” a été interprétée comme
la fonction à décorer, puisqu’elle a été passée comme premier argument, donc
logiquement considérée comme func
.
Vous vous souvenez du chapitre précédent ? Notre code ne préserve pas la
fonction décorée. Rajoutons un peu de documentation et appelons foobar.__name__
et foobar.__doc__
pour s’en convaincre :
# -*- coding: utf-8 -*-
def decorate(func=None, arg1=None, arg2=None, arg3=None):
print u'Je suis dans la fonction "decorate".'
def decorated(func):
print u'Je suis dans la fonction "decorated".'
def wrapper(*args, **kwargs):
"""Je suis la documentation de decorate."""
print u'Je suis dans la fonction "wrapper".'
print u"Les arguments du décorateurs sont : %s, %s, %s." % (arg1, arg2, arg3)
print u"Pré-traitement."
print u"Exécution de la fonction %s." % func.__name__
response = func(*args, **kwargs)
print u"Post-traitement."
return response
return wrapper
if func is None:
# Le décorateur est appellé avec des arguments
def decorator(func):
return decorated(func)
return decorator
# Le décorateur est appellé sans arguments
return decorated(func)
@decorate(arg1="Python", arg2="Haskell")
def foobar():
"""Je suis la documentation de foobar."""
print u"Je suis foobar, je vous reçois 5 sur 5."
print foobar.__name__
print foobar.__doc__
Exécutons :
Je suis dans la fonction "decorate".
Je suis dans la fonction "decorated".
wrapper
Je suis la documentation de decorate.
Mauvais. Faisons appel à functools.wraps
:
# -*- coding: utf-8 -*-
import functools
def decorate(func=None, arg1=None, arg2=None, arg3=None):
print u'Je suis dans la fonction "decorate".'
def decorated(func):
print u'Je suis dans la fonction "decorated".'
@functools.wraps(func)
def wrapper(*args, **kwargs):
"""Je suis la documentation de decorate."""
print u'Je suis dans la fonction "wrapper".'
print u"Les arguments du décorateurs sont : %s, %s, %s." % (arg1, arg2, arg3)
print u"Pré-traitement."
print u"Exécution de la fonction %s." % func.__name__
response = func(*args, **kwargs)
print u"Post-traitement."
return response
return wrapper
if func is None:
# Le décorateur est appellé avec des arguments
def decorator(func):
return decorated(func)
return decorator
# Le décorateur est appellé sans arguments
return decorated(func)
@decorate(arg1="Python", arg2="Haskell")
def foobar():
"""Je suis la documentation de foobar."""
print u"Je suis foobar, je vous reçois 5 sur 5."
print foobar.__name__
print foobar.__doc__
Exécutons :
Je suis dans la fonction "decorate".
Je suis dans la fonction "decorated".
foobar
Je suis la documentation de foobar.
C’est beaucoup mieux.
Exemple concret avec Django
Vous connaissez la fonction render_to_response
de Django ? C’est un raccourci pour rendre du contenu dans un template. Cette
fonction prend pour arguments : le nom du template (requis), un dictionnaire
contenant les variables/valeurs à ajouter dans le contexte du template
(optionnel), une instance de Context
ou RequestContext
(si on souhaite
bénéficier des context processors) dans laquelle sera injecté le dictionnaire
(par défaut, une instance de Context
) et le type MIME de la réponse (par
défaut, celui défini par settings.DEFAULT_CONTENT_TYPE
).
Exemple :
# -*- coding: utf-8 -*-
from django.shortcuts import render_to_response
def home(request):
"""
Page d'accueil.
"""
return render_to_response('home.html', {'foo': 'bar'})
Dans cet exemple, nous n’accédons pas aux context processors de Django. Pour
pour avoir accéder à ces fameux context processors, nous devons plutôt
utiliser une instance RequestContext
:
# -*- coding: utf-8 -*-
from django.shortcuts import render_to_response
from django.template import RequestContext
def home(request):
"""
Page d'accueil.
"""
return render_to_response(
'home.html',
{"foo": "bar"},
context_instance=RequestContext(request))
On est bien d’accord, c’est un peu verbeux. Si vous êtes un développeur Django
rusé, vous utilisez certainement les vues génériques (nous allons y venir).
Comme nous ne sommes pas censé savoir qu’il existe déjà une solution
prête-à-l’emploi, implémentons un décorateur pour rendre ce code un peu plus
concis. Nous allons le nommer render
.
Implémentation :
# -*- coding: utf-8 -*-
import functools
from django.shortcuts import render_to_response
from django.template import RequestContext
def render(template):
def decorated(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
"""
Raccourci du raccourci ``django.shortcuts.render_to_response``.
"""
context = func(*args, **kwargs)
return render_to_response(
template,
context,
context_instance=RequestContext(args[0]))
return wrapper
return decorated
@render('home.html')
def home(request):
"""
Page d'accueil.
"""
return {
'title': u"Home",
'description': u"This is the homepage.",
'view_name': home.__name__,
'view_doc': home.__doc__,
}
Nos vues n’ont plus qu’à retourner un dictionnaire des variables/valeurs à
injecter dans le context du template, que nous donnons comme argument au
décorateur. Notre décorateur render
se charge du reste. Les variables
view_name
et view_doc
doivent respectivement retourner le nom et la
documentation de la vue, et non celles du décorateur. Merci functools.wrap
.
Oui mais… On a réinventé la roue. Django propose déjà des
vues génériques,
dont notamment direct_to_template.
Nous n’avons, d’ailleurs, même plus besoin d’écrire la moindre vue. On spécifie
tout ça dans notre module urls.py
.
Notre code se limite à :
urlpatterns = patterns('',
url(r'^foobar/$', 'django.views.generic.simple.direct_to_template', {
'template': 'foobarapp/index.html',
'extra_context': {
"foo": "bar",
}},
name="foobar",
),
)
Merci les vues génériques. Il est vivement recommandé d’en abuser.
Un peu de lecture
Pour en savoir plus, n’hésitez pas à consulter ces liens :
- Python Decorators
- PEP 318 – Decorators for Functions and Methods
- Python Decorator Library
- Freshbooks Blog – Logging actions with Python decorators (part 1)
- Freshbooks Blog – Logging actions with Python decorators (part 1)
- Stackoverflow – Understanding Python Decorators
- IBM – Charming Python: Decorators make magic easy
- Artima – Decorators I
- Artima – Decorators II
- Artima – Decorators III
- Siafoo – Python Decorators Don’t Have to be (that) Scary
- Valued Lessons – Easy Python Decorators with the decorator decorator
- Kent’s Korner – Decorators
- Elf Sternberg – Python: Decorators With Arguments (with bonus Django goodness)
- Michele Simionato – The decorator module
- The Usware Blog – Understanding decorators
- Gawel.org – Les décorateurs Python
Conclusion
Les décorateurs sont une fonctionnalité très puissante et très utilisée du
langage Python. Python, lui-même, en propose de nombreux. Nous n’avons
qu’abordé functools.wraps
, mais il en existe beaucoup d’autres que vous
rencontrerez certainement un jour dans vos projets. Cet article n’est qu’une
simple introduction tant les possibilités offertes sont larges. Ils sont un
point fort du langage, avec sa syntaxe et son écosystème. Ne vous en privez
pas. À vos décorateurs !