2.23. Dekoratory

Dekorator to fragment składni, który opakowuje funkcję (lub metodę) w inną funkcję. Opakowanie może dodać zachowanie przed oryginalnym wywołaniem i po nim, zastąpić wartość zwracaną lub dołączyć metadane. Postać jest następująca:

@wrapper
def f():
    ...

Wiersz @wrapper jest równoważny f = wrapper(f) – funkcja f jest budowana normalnie, następnie przekazywana do wrapper, a wynik zostaje ponownie przypisany do nazwy f.

Funkcja wchodzi do ramki "opakowania" po lewej stronie i wyłania się po prawej jako udekorowana funkcja.

Dekorator przyjmuje funkcję i zwraca nową funkcję.

2.23.1. Wbudowane dekoratory metod

Kilka dekoratorów jest dostarczanych z Pythonem i jest używanych wewnątrz ciał klas.

2.23.1.1. @property

Zamienia metodę w obliczany atrybut. Wywołujący odwołuje się do niego tak, jakby był zwykłym atrybutem (bez nawiasów), ale przy każdym odczycie uruchamiana jest metoda:

class Circle:
    def __init__(self, radius):
        self.radius = radius

    @property
    def area(self):
        return 3.14159 * self.radius * self.radius

c = Circle(5)
print(c.area)             # 78.53975  -- no parentheses

Używaj tego, gdy atrybut, który wygląda na prosty, wymaga drobnego obliczenia w tle. Jeśli praca jest kosztowna, lepiej użyć zwykłej metody – wywołujący nie oczekują, że odczyty atrybutów będą wolne.

2.23.1.2. @classmethod

Definiuje metodę, która jako pierwszy argument otrzymuje klasę zamiast instancji. Pierwszy parametr jest zwyczajowo nazywany cls:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    @classmethod
    def origin(cls):
        return cls(0, 0)

p = Point.origin()

Metody klasowe to standardowy sposób na dostarczenie alternatywnych konstruktorów – funkcji fabrykujących, które zwracają instancję zbudowaną w niedomyślny sposób.

2.23.1.3. @staticmethod

Definiuje metodę, która nie otrzymuje ani instancji, ani klasy – jest to po prostu zwykła funkcja, która ze względów organizacyjnych żyje w przestrzeni nazw klasy:

class Temperature:
    @staticmethod
    def c_to_f(c):
        return c * 9 / 5 + 32

Temperature.c_to_f(100)   # 212.0

Używaj tego oszczędnie; jeśli funkcja naprawdę nie ma nic wspólnego ze stanem klasy, zwykła funkcja na poziomie modułu jest zazwyczaj czytelniejsza.

2.23.2. Pisanie własnego dekoratora

Dekorator to funkcja, która przyjmuje funkcję i zwraca funkcję. Minimalna postać:

def log_calls(func):
    def wrapper(*args, **kwargs):
        print("calling", func.__name__)
        return func(*args, **kwargs)
    return wrapper

@log_calls
def add(a, b):
    return a + b

add(2, 3)

Wynik:

calling add

wrapper domyka func i przekazuje mu wszystko. *args / **kwargs pozwala mu działać na dowolnej funkcji, nie tylko dwuargumentowej. Ten wzorzec jest podstawą bardziej rozbudowanych dekoratorów (mierzenie czasu, buforowanie, ponawianie po niepowodzeniu), ale rdzeń jest zawsze taki sam: przyjmij funkcję, zwróć funkcję.