Dekoratory

https://docs.python.org/3/reference/compound_stmts.html#function-definitions

WPROWADZENIE

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)

DEKORATORY FUNKCJI I METOD


# 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__)