Sé que esta pregunta es antigua, pero algunos de los comentarios son nuevos, y si bien 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 verificar si hay un solo argumento y es callabe (NOTA: serán necesarias verificaciones adicionales si su decorador solo toma 1 argumento y resulta ser un objeto invocable):
def decorator(*args, **kwargs):
if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
else:
En el primer caso, haces lo que hace cualquier decorador normal, devuelve una versión modificada o envuelta de la función pasada.
En el segundo caso, devuelve un decorador 'nuevo' que de alguna manera usa la información pasada con * args, ** kwargs.
Esto está bien y todo, pero tener que escribirlo para cada decorador que hagas 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 el siguiente decorador decorador, podemos decorar nuestros decoradores para que puedan usarse 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]):
return f(args[0])
else:
return lambda realf: f(realf, *args, **kwargs)
return new_dec
Ahora, podemos decorar a nuestros decoradores con @doublewrap, y trabajarán con y sin argumentos, con una advertencia:
Lo señalé anteriormente, pero debería repetir aquí, la verificación en este decorador hace una suposición sobre los argumentos que un decorador puede recibir (es decir, que no puede recibir un solo argumento invocable). Dado que ahora lo estamos haciendo aplicable a cualquier generador, debe tenerse en cuenta o modificarse si se contradice.
Lo siguiente demuestra 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
@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
es notablemente poco informativo. Sugeriría que es una mala idea. Usa la primera forma y simplifica mucho tu vida.