Sé que esta pregunta es antiguo, pero algunos de los comentarios son nuevos , y aunque todas las soluciones viables son esencialmente las mismas, la mayoría de ellas no son muy limpias ni fáciles de leer.
Como dice la respuesta de thobe, la única forma de manejar ambos casos es verificar ambos escenarios. La forma más fácil es simplemente para comprobar para ver si hay un solo argumento y es callabe (NOTA: Las comprobaciones adicionales serán necesarios si su decorador sólo toma 1 argumento y pasa a ser un objeto invocable):
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)
En el primer caso, usted hace lo que hace cualquier decorador normal, devuelve una versión modificada o envuelta de la función aprobada.
En el segundo caso, devuelve un "nuevo" decorador que de alguna manera utiliza la información que se transmite con * args, ** kwargs.
Esto está bien y todo, pero tener que escribirlo para cada decorador que haga puede ser bastante molesto y no tan limpio. En cambio, sería bueno poder modificar automágicamente nuestros decoradores sin tener que volver a escribirlos ... ¡pero para eso están los decoradores!
Usando la siguiente decorador decorador, podemos deocrate nuestros decoradores para que puedan ser utilizados con o sin argumentos:
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
Ahora, podemos decorar nuestros decoradores con @doublewrap, y que van a trabajar con y sin argumentos, con una advertencia:
Anoté anteriormente pero debería repetir aquí, el cheque en este decorador hace una suposición sobre los argumentos que un decorador puede recibir (a saber, que no puede recibir un solo argumento invocable). Como ahora lo hacemos aplicable a cualquier generador, debe tenerse en cuenta o modificarse si se lo contradice.
La siguiente muestra su uso:
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
La apariencia predeterminada '@ redirect_output' es notablemente poco informativa. Sugeriría que es una mala idea. Usa la primera forma y simplifica tu vida mucho. –
pregunta interesante, hasta que lo vi y revisé la documentación, habría supuesto que @f era lo mismo que @f(), y aún así creo que debería ser, para ser sincero (cualquier argumento proporcionado sería solo adherido al argumento de la función) – rog