Используй функции как полноценные объекты, чтобы писать надёжные, параметризуемые и прозрачные декораторы.
Открыть этот урок в KodokonВ Python функция - такой же объект, как и любой другой: её можно присвоить переменной, передать аргументом, вернуть. Функция высшего порядка принимает или возвращает функцию - sorted(data, key=...) как раз такая. Добавь замыкания, где внутренняя функция захватывает переменные охватывающей и сохраняет их живыми после её возврата, - и у тебя есть все ингредиенты декораторов.
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Декоратор - это функция, которая получает функцию и возвращает другую - обычно wrapper, добавляющий поведение вокруг вызова. Синтаксис @timed над def slow_sum - в точности сахар для slow_sum = timed(slow_sum). Обёртка принимает *args, kwargs, чтобы не зависеть от сигнатуры, и возвращает** результат декорируемой функции - забыть об этом классическая ошибка, из-за которой любое возвращаемое значение превращается в None.
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__ часто читается лучше. Оставь декораторы для сквозных задач: логирование, кеширование, повторные попытки, контроль доступа. Декоратор, который меняет бизнес-логику, прячет от читателя самое главное.
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")