Je sais que cette question est vieux, mais quelques-uns 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 propres ou faciles à lire.
Comme la réponse de thobe l'indique, la seule façon de gérer les deux cas est de vérifier les deux scénarios. Le plus simple est simplement de vérifier s'il y a un seul argument et il est callabe (NB: Les contrôles supplémentaires seront nécessaires si votre décorateur ne prend 1 argument et il arrive à être un objet appelable):
def decorator(*args, **kwargs):
if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
# called as @decorator
else:
# called as @decorator(*args, **kwargs)
Dans le premier cas, vous faites ce que fait un décorateur normal, renvoyer une version modifiée ou encapsulée de la fonction transmise. Dans le second cas, vous renvoyez un 'nouveau' décorateur qui utilise en quelque sorte les informations transmises avec * args, ** kwargs. C'est très bien, mais le fait de l'écrire pour chaque décorateur que vous faites peut être assez ennuyeux et pas aussi propre. Au lieu de cela, il serait bien de pouvoir modifier automagiquement nos décorateurs sans avoir à les réécrire ... mais c'est ce à quoi servent les décorateurs!
Utilisation du décorateur de décorateur suivant, nous pouvons deocrate 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]):
# actual decorated function
return f(args[0])
else:
# decorator arguments
return lambda realf: f(realf, *args, **kwargs)
return new_dec
Maintenant, nous pouvons décorer nos décorateurs avec @doublewrap, et ils vont travailler et sans arguments, avec une réserve:
Je note ci-dessus mais je dois 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 l'appliquons maintenant à n'importe quel générateur, il faut le garder à l'esprit ou le modifier s'il est contredit.
Ce qui suit dé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
# try normal
@mult
def f(x, y):
return x + y
# try args
@mult(3)
def f2(x, y):
return x*y
# try kwargs
@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
L'aspect par défaut '@ redirect_output' est remarquablement non-informatif. Je dirais que c'est une mauvaise idée. Utilisez le premier formulaire et simplifiez votre vie. –
question intéressante - jusqu'à ce que je l'ai vu et regardé la documentation, j'aurais supposé que @f était le même que @f(), et je pense toujours qu'il devrait être, pour être honnête (tous les arguments fournis seraient juste collé sur l'argument de la fonction) – rog