Question
Comment puis-je écrire joliment un décorateur?
Les problèmes particuliers incluent: la compatibilité avec d’autres décorateurs, la préservation des signatures, etc.
Je voudrais éviter si possible de dépendre du module décorateur, mais s'il y avait suffisamment d'avantages, je le prendrais en compte.
Connexes
- Conservation des signatures des fonctions décorées - question beaucoup plus précise. La solution ici est d’utiliser le module de décorateur tiers en annotant le décorateur avec @ decorator.decorator
La solution
Écrire un bon décorateur n’est pas différent d’écrire une bonne fonction. Ce qui signifie, idéalement, utiliser des docstrings et s’assurer que le décorateur est inclus dans votre framework de test.
Vous devez absolument utiliser la bibliothèque decorator
ou, mieux, le décorateur functools.wraps ()
dans la bibliothèque standard (à partir de la version 2.5).
Au-delà de cela, il est préférable de garder vos décorateurs concentrés et bien conçus. N'utilisez pas * args
ou ** kw
si votre décorateur attend des arguments spécifiques. Et ne remplissez que les arguments que vous attendez, donc au lieu de:
def keep_none(func):
def _exec(*args, **kw):
return None if args[0] is None else func(*args, **kw)
return _exec
... utilisez ...
def keep_none(func):
"""Wraps a function which expects a value as the first argument, and
ensures the function won't get called with *None*. If it is, this
will return *None*.
>>> def f(x):
... return x + 5
>>> f(1)
6
>>> f(None) is None
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
>>> f = keep_none(f)
>>> f(1)
6
>>> f(None) is None
True"""
@wraps(func)
def _exec(value, *args, **kw):
return None if value is None else func(value, *args, **kw)
return _exec
Autres conseils
Utilisez functools pour conserver le nom et la doc. La signature ne sera pas conservée.
Directement de doc .
>>> from functools import wraps
>>> def my_decorator(f):
... @wraps(f)
... def wrapper(*args, **kwds):
... print 'Calling decorated function'
... return f(*args, **kwds)
... return wrapper
...
>>> @my_decorator
... def example():
... """Docstring"""
... print 'Called example function'
...
>>> example()
Calling decorated function
Called example function
>>> example.__name__
'example'
>>> example.__doc__
'Docstring'