Kodokon kodokon.com

Декораторы и функции высшего порядка

Используй функции как полноценные объекты, чтобы писать надёжные, параметризуемые и прозрачные декораторы.

10 мин · 3 вопросов

Открыть этот урок в Kodokon

В Python функция - такой же объект, как и любой другой: её можно присвоить переменной, передать аргументом, вернуть. Функция высшего порядка принимает или возвращает функцию - sorted(data, key=...) как раз такая. Добавь замыкания, где внутренняя функция захватывает переменные охватывающей и сохраняет их живыми после её возврата, - и у тебя есть все ингредиенты декораторов.

PYTHON
def make_multiplier(factor):
    def multiply(value):
        return value * factor
    return multiply

double = make_multiplier(2)
triple = make_multiplier(3)
print(double(10), triple(10))  # 20 30
Замыкание: multiply захватывает factor

Декоратор - это функция, которая получает функцию и возвращает другую - обычно wrapper, добавляющий поведение вокруг вызова. Синтаксис @timed над def slow_sum - в точности сахар для slow_sum = timed(slow_sum). Обёртка принимает *args, kwargs, чтобы не зависеть от сигнатуры, и возвращает** результат декорируемой функции - забыть об этом классическая ошибка, из-за которой любое возвращаемое значение превращается в None.

PYTHON
import functools
import time

def timed(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        delay = time.perf_counter() - start
        print(f"{func.__name__}: {delay:.4f}s")
        return result
    return wrapper

@timed
def slow_sum(n):
    return sum(range(n))

print(slow_sum(1_000_000))
Декоратор замера времени, готовый к продакшену

Чтобы параметризовать декоратор (@repeat(times=3)), нужен ещё один уровень: фабрика, которая получает параметры и возвращает декоратор, а тот, в свою очередь, возвращает обёртку. Три вложенные функции - такова цена гибкости. Если состояние усложняется, класс с методом __call__ часто читается лучше. Оставь декораторы для сквозных задач: логирование, кеширование, повторные попытки, контроль доступа. Декоратор, который меняет бизнес-логику, прячет от читателя самое главное.

PYTHON
import functools

def repeat(times):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            result = None
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(times=3)
def greet(name):
    print(f"Hello {name}")

greet("Ada")
Параметризованный декоратор: фабрика, декоратор, обёртка

Проверка знаний

Убедись, что запомнил ключевые моменты этого урока.

  1. Чему равнозначен синтаксис @timed, поставленный над def slow_sum?
    • timed(slow_sum())
    • slow_sum = timed(slow_sum)
    • slow_sum.timed = True
  2. Какую проблему решает @functools.wraps?
    • Ускоряет вызов декорируемой функции
    • Сохраняет имя и докстринг декорируемой функции
    • Делает декоратор совместимым с методами класса
  3. В @repeat(times=3) что возвращает вызов repeat(times=3)?
    • Результат декорируемой функции
    • Декоратор, готовый принять функцию
    • Сразу финальную обёртку
    • Список из трёх результатов