Sei que essa pergunta é antiga, mas alguns dos comentários são novos e, embora todas as soluções viáveis sejam essencialmente as mesmas, a maioria delas não é muito clara ou fácil de ler.
Como diz a resposta de Thobe, a única maneira de lidar com os dois casos é verificar os dois cenários. A maneira mais fácil é simplesmente verificar se há um único argumento e se ele pode ser chamado (OBSERVAÇÃO: verificações extras serão necessárias se o seu decorador receber apenas 1 argumento e for um objeto que pode ser chamado):
def decorator(*args, **kwargs):
if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
else:
No primeiro caso, você faz o que qualquer decorador normal faz, retorna uma versão modificada ou encapsulada da função passada.
No segundo caso, você retorna um 'novo' decorador que de alguma forma usa as informações passadas com * args, ** kwargs.
Isso é bom e tudo, mas ter que escrever isso para cada decorador que você fizer pode ser muito chato e não tão limpo. Em vez disso, seria bom ser capaz de modificar automaticamente nossos decoradores sem ter que reescrevê-los ... mas é para isso que servem os decoradores!
Usando o seguinte decorador decorador, podemos deocrate nossos decoradores para que eles possam ser usados com ou sem 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
Agora, podemos decorar nossos decoradores com @doublewrap, e eles trabalharão com e sem argumentos, com uma ressalva:
Eu observei acima, mas devo repetir aqui, a verificação neste decorador faz uma suposição sobre os argumentos que um decorador pode receber (ou seja, que ele não pode receber um único argumento que pode ser chamado). Já que o estamos tornando aplicável a qualquer gerador agora, ele precisa ser mantido em mente ou modificado se for contestado.
O seguinte demonstra seu 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
é notavelmente pouco informativa. Eu diria que é uma má ideia. Use o primeiro formulário e simplifique muito a sua vida.