So che questa domanda è vecchia, ma alcuni commenti sono nuovi e, sebbene tutte le soluzioni praticabili siano essenzialmente le stesse, la maggior parte di esse non sono molto chiare o facili da leggere.
Come dice la risposta di thobe, l'unico modo per gestire entrambi i casi è controllare entrambi gli scenari. Il modo più semplice è semplicemente controllare se c'è un singolo argomento ed è callabe (NOTA: saranno necessari controlli extra se il tuo decoratore accetta solo 1 argomento e sembra essere un oggetto richiamabile):
def decorator(*args, **kwargs):
if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
else:
Nel primo caso, fai quello che fa un normale decoratore, restituisci una versione modificata o incartata della funzione passata.
Nel secondo caso, restituisci un "nuovo" decoratore che in qualche modo utilizza le informazioni passate con * args, ** kwargs.
Va bene tutto, ma doverlo scrivere per ogni decoratore che fai può essere piuttosto fastidioso e non così pulito. Invece, sarebbe bello poter modificare automaticamente i nostri decoratori senza doverli riscrivere ... ma è a questo che servono i decoratori!
Utilizzando il seguente decoratore decoratore, possiamo deocratizzare i nostri decoratori in modo che possano essere utilizzati con o senza argomenti:
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
Ora possiamo decorare i nostri decoratori con @doublewrap, e lavoreranno con e senza argomenti, con un avvertimento:
Ho notato sopra ma dovrei ripetere qui, il controllo in questo decoratore fa un'ipotesi sugli argomenti che un decoratore può ricevere (vale a dire che non può ricevere un singolo argomento richiamabile). Dal momento che lo stiamo rendendo applicabile a qualsiasi generatore ora, deve essere tenuto presente o modificato se sarà contraddetto.
Quanto segue dimostra il suo utilizzo:
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
è notevolmente disinformativo. Suggerirei che sia una cattiva idea. Usa la prima forma e semplifica molto la tua vita.