2.23. Декоратори

Декоратор – це синтаксична конструкція, яка загортає функцію (або метод) в іншу функцію. Обгортка може додавати поведінку до і після оригінального виклику, замінювати значення, що повертається, або прикріплювати метадані. Синтаксис:

@wrapper
def f():
    ...

Рядок @wrapper еквівалентний f = wrapper(f) – функція f будується звичайним чином, потім передається до wrapper, а результат знову прив’язується до імені f.

A function entering a "wrapper" box on the left and emerging on the right as a decorated function.

Декоратор приймає функцію та повертає нову функцію.

2.23.1. Вбудовані декоратори методів

Кілька декораторів входять до складу Python і використовуються всередині тіл класів.

2.23.1.1. @property

Перетворює метод на обчислюваний атрибут. Той, хто викликає, звертається до нього як до звичайного атрибута (без дужок), але при кожному читанні виконується метод:

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

Використовуйте його, коли атрибут, що виглядає простим, потребує невеликого обчислення. Якщо операція ресурсомістка, надайте перевагу звичайному методу – той, хто викликає, не очікує, що читання атрибутів буде повільним.

2.23.1.2. @classmethod

Визначає метод, який отримує клас як перший аргумент замість екземпляра. Перший параметр традиційно називають 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()

Методи класу є стандартним способом надання альтернативних конструкторів – фабричних функцій, які повертають екземпляр, побудований нестандартним способом.

2.23.1.3. @staticmethod

Визначає метод, який не отримує ані екземпляр, ані клас – це просто звичайна функція, що живе в просторі імен класу з організаційних причин:

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

Temperature.c_to_f(100)   # 212.0

Використовуйте його помірно; якщо функція дійсно не має нічого спільного зі станом класу, звичайна функція на рівні модуля зазвичай чистіша.

2.23.2. Написання власного декоратора

Декоратор – це функція, яка приймає функцію та повертає функцію. Мінімальний шаблон:

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)

Виведення:

calling add

wrapper замикається над func і пересилає все до нього. *args / **kwargs дозволяє йому працювати з будь-якою функцією, а не лише з функціями з двома аргументами. Цей шаблон є основою більш складних декораторів (вимірювання часу, кешування, повтор при помилці), але суть завжди одна: отримати функцію на вході, повернути функцію на виході.