https://docs.python.org/3/reference/compound_stmts.html#function-definitions
Dekoratory funkcji udostępniają możliwość określania specjalnych trybów operacji dla funkcji, opakowując je w dodatkową warstwę logiki zaimplementowanej jako kolejna funkcja. Istnieje sporo funkcji wbudowanych, które można użyć w charakterze dekoratora. Można także stworzyć własny dekorator. Składnia dekoratorów pojawiła się w Pythonie 2.4 (PEP 318).
Od Pythona 2.6 dekoratory mają możliwość współpracy z klasami (PEP 3129). Wtedy uruchamiane są na końcu instrukcji class, wiążąc wynik swojego wykonania z nazwą klasy.
# Składnia dekoratorów funkcji/metod ma postać ... @decorator2 @decorator1 def func(arg1, arg2, ...): pass # ... co jest równoważne ... def func(arg1, arg2, ...): pass func = decorator2(decorator1(func))
# Składnia dekoratora z argumentami ... @decorator_maker(argA, argB, ...) def func(arg1, arg2, ...): pass # ... co jest równoważne ... func = decorator_maker(argA, argB, ...)(func)
# Poradnik do dekoratorów można znaleźć na blogu "The Code Ship", # http://thecodeship.com/patterns/guide-to-python-function-decorators/
# Dekorator jako funkcja z zagnieżdżoną funkcją. from functools import wraps def mytracer(func): @wraps(func) def wrapper(*arguments, **keywords): print("mytracer working ...") # dodatkowy komunikat return func(*arguments, **keywords) # func zaszyte w wrapper #wrapper.__name__ = func.__name__ # w przybliżeniu #wrapper.__doc__ = func.__doc__ # w przyblizeniu return wrapper @mytracer def spam(a, b, c): #def spam(a: "arg1", b: "arg2", c: "arg3") -> "summation": # adnotacje """To jest funkcja spam.""" print("{} {} {}".format(a, b, c)) return a + b + c print(spam(1, 2, 3)) print(spam("a", "b", "c")) print(spam(c=1, b=2, a=3)) print(spam.__name__) # bez @wraps() jest 'wrapper' print(spam.__doc__) # bez @wraps() jest None print(spam.__annotations__) # bez @wraps() jest {}
# Tworzymy własny dekorator funkcji będący klasą (jeden ze sposobów). # Ten sposób nie zadziała dla metod w klasach. class Tracer: """To jest klasa Tracer.""" def __init__(self, func): self.calls = 0 self.func = func self.__name__ = func.__name__ # bez tego AttributeError self.__doc__ = func.__doc__ # bez tego docstring klasy Tracer def __call__(self, *arguments, **keywords): self.calls += 1 print("call {} to {}".format(self.calls, self.func.__name__)) return self.func(*arguments, **keywords) # Do funkcji spam() dodajemy warstwę logiki. # spam = Tracer(spam) @Tracer def spam(a, b, c): """To jest funkcja spam.""" print("{} {} {}".format(a, b, c)) return a + b + c print(spam(1, 2, 3)) # call 1 to spam print(spam("a", "b", "c")) # call 2 to spam print(spam(c=1, b=2, a=3)) # call 3 to spam print(spam.__name__) # AttributeError: 'Tracer' object has no attribute '__name__' print(spam.__doc__)
# https://www.geeksforgeeks.org/class-as-decorator-in-python/ import time class Tracer: def __init__(self, func): # tu łapiemy funkcje do przetworzenia self.func = func self.__name__ = func.__name__ # bez tego AttributeError self.__doc__ = func.__doc__ # bez tego docstring klasy Tracer def __call__(self, *arguments, **keywords): start_time = time.time() reply = self.func(*arguments, **keywords) end_time = time.time() print("Execution took {} seconds".format(end_time - start_time)) return reply
# Tutaj instancja klasy będzie dekoratorem. # Jest możliwość przekazywania parametrów do dekoratora. from functools import wraps # Decorator factory to apply update_wrapper() to a wrapper function. class Tracer: def __init__(self): # tu mogą być parametry dekoratora self.calls = 0 def __call__(self, func): # tu łapiemy funkcję do przetworzenia @wraps(func) def wrapper(*arguments, **keywords): self.calls += 1 print("call {} to {}".format(self.calls, func.__name__)) return func(*arguments, **keywords) return wrapper # Do funkcji spam() dodajemy warstwę logiki. # spam = Tracer()(spam) @Tracer() def spam(a, b, c): """To jest funkcja spam.""" print("{} {} {}".format(a, b, c)) return a + b + c print(spam(1, 2, 3)) # call 1 to spam print(spam("a", "b", "c")) # call 2 to spam print(spam(c=1, b=2, a=3)) # call 3 to spam print(spam.__name__) # bez @wraps() będzie string 'wrapper' print(spam.__doc__)