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, ...):
instrukcje
# ... co jest równoważne ...
def func(arg1, arg2, ...):
instrukcje
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 przybliżeniu
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__)