Les décorateurs Python

Les décorateurs Python permettent d'effectuer des opérations sur une fonction à son initialisation. Les décorateurs sont eux même des fonctions qui retourne une fonction:

>>> def decorator(func):
...     func.is_decorate = True
...     return func

>>> @decorator
... def decorate():
...     pass

>>> decorate.is_decorate
True

Si l'on désire un décorateur prenant des arguments, il faut alors que notre décorateur retourne lui même un décorateur:

>>> def decorator(attempt):
...     def wrapper(func):
...         func.attempt = attempt
...         return func
...     return wrapper

Ici, notre décorateur renvois un décorateur qui renvois lui même la fonction:

>>> @decorator(True)
... def decorate(arg):
...     return arg

>>> decorate(True) == decorate.attempt
True

>>> decorate(None) == decorate.attempt
False

Dans le cas précédent, nous avons les moyens de faire une sorte de validation. Mais le décorateur pourrait très bien le faire lui même. C'est le but ! Cela ce complique alors encore un peu plus. Il faut cette fois 3 niveau de fonctions pour pouvoir prendre en compte les arguments passé à la fonctions:

>>> def decorator(attempt):
...     def wrapper(func):
...         def wrapped(arg):
...             result = func(arg)
...             if result != attempt:
...                raise ValueError('La fonction doit retourner %s '
...                                 'mais retourne %s' % (attempt,result))
...             return result
...         return wrapped
...     return wrapper

Vous remarquerez qu'ici le décorateur ne retourne pas la fonction elle même mais une autre fonction qui valide la fonction originale. En pratique, voici le résultat:

>>> @decorator(True)
... def decorate(arg):
...     return arg

>>> decorate(True)
True

>>> decorate(None)
Traceback (most recent call last):
...
ValueError: La fonction doit retourner True mais retourne None

Bien sûr, tout ceci fonctionne aussi pour des méthode de classe. Notre décorateur doit alors tenir compte du premier argument, la classe elle même:

>>> def decorator(attempt):
...     def wrapper(func):
...         def wrapped(self, arg):
...             result = func(self, arg)
...             if result != attempt:
...                raise ValueError('La fonction doit retourner %s '
...                                 'mais retourne %s' % (attempt,result))
...             return result
...         return wrapped
...     return wrapper


>>> class Gruik(object):
...    @decorator(True)
...    def is_true(self,arg):
...        return arg
...    @decorator(None)
...    def is_none(self,arg):
...        return arg

>>> gruik = Gruik()
>>> gruik.is_true(True)
True
>>> gruik.is_none(False)
Traceback (most recent call last):
...
ValueError: La fonction doit retourner None mais retourne False

Tout de même sympathique, n'est ce pas ?

Pour en savoir plus, je vous renvois au wiki python. Ainsi qu'au nouveautés de python 2.5 concernant les décorateurs ou le module functools permet d'obtenir des décorateur reprenant les attributs de la fonction originale.