Wiem, że to pytanie jest stare, ale niektóre komentarze są nowe i chociaż wszystkie realne rozwiązania są zasadniczo takie same, większość z nich nie jest zbyt przejrzysta ani łatwa do odczytania.
Jak mówi odpowiedź thobe, jedynym sposobem rozwiązania obu przypadków jest sprawdzenie obu scenariuszy. Najłatwiej jest po prostu sprawdzić, czy jest pojedynczy argument i jest on wywoływany (UWAGA: dodatkowe sprawdzenia będą konieczne, jeśli dekorator przyjmuje tylko 1 argument i jest to obiekt możliwy do wywołania):
def decorator(*args, **kwargs):
if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
else:
W pierwszym przypadku robisz to, co robi każdy normalny dekorator, zwracając zmodyfikowaną lub opakowaną wersję przekazanej funkcji.
W drugim przypadku zwracasz „nowy” dekorator, który w jakiś sposób wykorzystuje informacje przekazane przez * args, ** kwargs.
To wszystko w porządku, ale napisanie tego dla każdego dekoratora, którego tworzysz, może być dość denerwujące i nie tak czyste. Zamiast tego byłoby miło móc automagicznie modyfikować nasze dekoratory bez konieczności ich ponownego pisania ... ale po to są dekoratorzy!
Korzystając z następującego dekoratora dekoratora, możemy zdekokrować nasze dekoratory, aby mogły być używane z argumentami lub bez:
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
Teraz możemy ozdobić nasze dekoratory @doublewrap i będą one działać z argumentami i bez nich, z jednym zastrzeżeniem:
Zauważyłem powyżej, ale powinienem tutaj powtórzyć, sprawdzenie w tym dekoratorze zakłada, że argumenty, które może otrzymać dekorator (a mianowicie, że nie może otrzymać pojedynczego, wywoływalnego argumentu). Ponieważ teraz sprawiamy, że ma on zastosowanie do dowolnego generatora, należy o tym pamiętać lub zmodyfikować, jeśli będzie się temu zaprzeczać.
Poniżej przedstawiono jego zastosowanie:
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_outputjest wyjątkowo mało informacyjny. Sugerowałbym, że to zły pomysł. Skorzystaj z pierwszej formy i znacznie uprość sobie życie.