Je sais que cette question est ancienne, mais certains des commentaires sont nouveaux et, bien que toutes les solutions viables soient essentiellement les mêmes, la plupart d'entre elles ne sont pas très claires ou faciles à lire.
Comme le dit la réponse de Thobe, la seule façon de gérer les deux cas est de vérifier les deux scénarios. Le moyen le plus simple est simplement de vérifier s'il y a un seul argument et qu'il est callabe (NOTE: des vérifications supplémentaires seront nécessaires si votre décorateur ne prend qu'un seul argument et qu'il se trouve être un objet appelable):
def decorator(*args, **kwargs):
if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
else:
Dans le premier cas, vous faites ce que fait n'importe quel décorateur normal, renvoyez une version modifiée ou encapsulée de la fonction passée.
Dans le second cas, vous retournez un 'nouveau' décorateur qui utilise d'une manière ou d'une autre les informations transmises avec * args, ** kwargs.
C'est bien et tout, mais devoir l'écrire pour chaque décorateur que vous créez peut être assez ennuyeux et pas aussi propre. Au lieu de cela, ce serait bien de pouvoir modifier automatiquement nos décorateurs sans avoir à les réécrire ... mais c'est à cela que servent les décorateurs!
En utilisant le décorateur décorateur suivant, nous pouvons déocrate nos décorateurs afin qu'ils puissent être utilisés avec ou sans arguments:
def doublewrap(f):
'''
a decorator decorator, allowing the decorator to be used as:
@decorator(with, arguments, and=kwargs)
or
@decorator
'''
@wraps(f)
def new_dec(*args, **kwargs):
if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
return f(args[0])
else:
return lambda realf: f(realf, *args, **kwargs)
return new_dec
Maintenant, nous pouvons décorer nos décorateurs avec @doublewrap, et ils fonctionneront avec et sans arguments, avec une mise en garde:
J'ai noté ci-dessus mais je dois le répéter ici, la vérification dans ce décorateur fait une hypothèse sur les arguments qu'un décorateur peut recevoir (à savoir qu'il ne peut pas recevoir un seul argument appelable). Puisque nous le rendons applicable à n'importe quel générateur maintenant, il doit être gardé à l'esprit, ou modifié s'il est contredit.
Ce qui suit montre son utilisation:
def test_doublewrap():
from util import doublewrap
from functools import wraps
@doublewrap
def mult(f, factor=2):
'''multiply a function's return value'''
@wraps(f)
def wrap(*args, **kwargs):
return factor*f(*args,**kwargs)
return wrap
@mult
def f(x, y):
return x + y
@mult(3)
def f2(x, y):
return x*y
@mult(factor=5)
def f3(x, y):
return x - y
assert f(2,3) == 10
assert f2(2,5) == 30
assert f3(8,1) == 5*7
@redirect_output
est remarquablement peu informative. Je dirais que c'est une mauvaise idée. Utilisez le premier formulaire et simplifiez-vous beaucoup la vie.