Mam dekoratora jak poniżej.
def myDecorator(test_func):
return callSomeWrapper(test_func)
def callSomeWrapper(test_func):
return test_func
@myDecorator
def someFunc():
print 'hello'
Chcę ulepszyć ten dekorator, aby zaakceptował inny argument, jak poniżej
def myDecorator(test_func,logIt):
if logIt:
print "Calling Function: " + test_func.__name__
return callSomeWrapper(test_func)
@myDecorator(False)
def someFunc():
print 'Hello'
Ale ten kod daje błąd,
TypeError: myDecorator () przyjmuje dokładnie 2 argumenty (podany 1)
Dlaczego funkcja nie jest przekazywana automatycznie? Jak jawnie przekazać tę funkcję do funkcji dekoratora?
Odpowiedzi:
Ponieważ wywołujesz dekorator jako funkcję, musi on zwrócić inną funkcję, która jest rzeczywistym dekoratorem:
def my_decorator(param): def actual_decorator(func): print("Decorating function {}, with parameter {}".format(func.__name__, param)) return function_wrapper(func) # assume we defined a wrapper somewhere return actual_decorator
Funkcja zewnętrzna otrzyma wszelkie argumenty, które przekażesz jawnie, i powinna zwrócić funkcję wewnętrzną. Funkcja wewnętrzna otrzyma funkcję dekoracyjną i zwróci zmodyfikowaną funkcję.
Zwykle chcesz, aby dekorator zmienił zachowanie funkcji, opakowując ją w funkcję opakowującą. Oto przykład, który opcjonalnie dodaje rejestrowanie, gdy funkcja jest wywoływana:
def log_decorator(log_enabled): def actual_decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): if log_enabled: print("Calling Function: " + func.__name__) return func(*args, **kwargs) return wrapper return actual_decorator
W
functools.wraps
kopiuje nazywać rzeczy takie jak imię i docstring do funkcji otoki, aby uczynić go bardziej podobna do pierwotnej funkcji.Przykładowe użycie:
>>> @log_decorator(True) ... def f(x): ... return x+1 ... >>> f(4) Calling Function: f 5
źródło
functools.wraps
jest zalecane - zachowuje oryginalną nazwę, ciąg dokumentów itp. Opakowanej funkcji.myDecorator
tutaj) funkcję zewnętrzną dekoratorem. Jest to wygodne dla użytkownika dekoratora, ale może być mylące, gdy próbujesz go napisać.log_decorator
przyjmujesz domyślny argument, nie możesz go użyć@log_decorator
, to musi być@log_decorator()
Żeby przedstawić inny punkt widzenia: składnię
@expr def func(...): #stuff
jest równa
def func(...): #stuff func = expr(func)
W szczególności
expr
może to być cokolwiek chcesz, o ile ma wartość wywoływalną. W szczególności szczególnościexpr
może być fabrykę dekorator: nadać mu pewne parametry i daje dekorator. Więc może lepszym sposobem na zrozumienie Twojej sytuacji jestdec = decorator_factory(*args) @dec def func(...):
który można następnie skrócić do
@decorator_factory(*args) def func(...):
Oczywiście, ponieważ wygląda jak
decorator_factory
jest dekorator, ludzie mają tendencję, aby wymienić je odzwierciedlać. Co może być mylące, gdy próbujesz podążać za poziomami pośrednictwa.źródło
Po prostu chcę dodać jakąś użyteczną sztuczkę, która pozwoli uczynić argumenty dekoratora opcjonalnymi. Pozwoli to również na ponowne użycie dekoratora i zmniejszenie zagnieżdżenia
import functools def myDecorator(test_func=None,logIt=None): if not test_func: return functools.partial(myDecorator, logIt=logIt) @functools.wraps(test_func) def f(*args, **kwargs): if logIt==1: print 'Logging level 1 for {}'.format(test_func.__name__) if logIt==2: print 'Logging level 2 for {}'.format(test_func.__name__) return test_func(*args, **kwargs) return f #new decorator myDecorator_2 = myDecorator(logIt=2) @myDecorator(logIt=2) def pow2(i): return i**2 @myDecorator def pow3(i): return i**3 @myDecorator_2 def pow4(i): return i**4 print pow2(2) print pow3(2) print pow4(2)
źródło
Po prostu inny sposób robienia dekoratorów. Uważam, że ten sposób jest najłatwiejszy do ogarnięcia głowy.
class NiceDecorator: def __init__(self, param_foo='a', param_bar='b'): self.param_foo = param_foo self.param_bar = param_bar def __call__(self, func): def my_logic(*args, **kwargs): # whatever logic your decorator is supposed to implement goes in here print('pre action baz') print(self.param_bar) # including the call to the decorated function (if you want to do that) result = func(*args, **kwargs) print('post action beep') return result return my_logic # usage example from here on @NiceDecorator(param_bar='baaar') def example(): print('example yay') example()
źródło
Teraz, jeśli chcesz wywołać funkcję
function1
z dekoratoremdecorator_with_arg
iw tym przypadku zarówno funkcja, jak i dekorator przyjmują argumenty,def function1(a, b): print (a, b) decorator_with_arg(10)(function1)(1, 2)
źródło