Привет! В прошлой статье мы познакомились с декораторами и поняли, что это такое. Теперь пришло время научиться писать свои декораторы. Начнём с самого простого — декоратора без аргументов.
В этой статье мы разберём:
- Структуру простейшего декоратора
- Как написать свой первый декоратор
- Сохранение метаданных
- Практические примеры
- Что нужно знать перед началом
- Основная часть
- Структура простейшего декоратора
- Простейший декоратор для логирования
- Декоратор для замера времени
- Декоратор для проверки прав
- Сохранение метаданных с @wraps
- Декоратор для повторных попыток (retry)
- Цепочка декораторов
- Задачи для закрепления
- Нюансы и подводные камни
- Сохранение метаданных
- Порядок декораторов
- Декоратор без возврата функции
- Частые ошибки и как их избежать
- Ошибка 1: Забыл *args и **kwargs
- Ошибка 2: Забыл @wraps
- Ошибка 3: Декоратор не возвращает функцию
- Шпаргалка
- Заключение / Выводы
- КВИЗ
- Что дальше?
Что нужно знать перед началом
Для этого урока тебе понадобится:
- Установленный Python
- Базовое понимание функций и декораторов (статья 1 в этом подменю)
Совет: Простейший декоратор — это функция, которая принимает функцию, добавляет поведение и возвращает новую функцию.
Основная часть
Структура простейшего декоратора
def decorator(func):
def wrapper(*args, **kwargs):
# Дополнительное поведение ДО вызова функции
result = func(*args, **kwargs)
# Дополнительное поведение ПОСЛЕ вызова функции
return result
return wrapperТри обязательных элемента:
- Внешняя функция
decorator, принимающаяfunc - Внутренняя функция
wrapper, которая обёртывает вызов - Возврат
wrapperизdecorator
Совет:
*argsи**kwargsпозволяют передавать любые аргументы в обёрнутую функцию.
Простейший декоратор для логирования
def logger(func):
def wrapper(*args, **kwargs):
print(f"Вызов функции: {func.__name__}")
result = func(*args, **kwargs)
print(f"Результат: {result}")
return result
return wrapper
@logger
def add(a, b):
return a + b
@logger
def multiply(a, b):
return a * b
print(add(3, 5)) # Вызов функции: add -> Результат: 8 -> 8
print(multiply(2, 4)) # Вызов функции: multiply -> Результат: 8 -> 8Декоратор для замера времени
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"Функция {func.__name__} выполнилась за {end - start:.4f} сек")
return result
return wrapper
@timer
def slow_function():
time.sleep(1.5)
return "Готово!"
slow_function() # Функция slow_function выполнилась за 1.5003 секДекоратор для проверки прав
def require_admin(func):
def wrapper(user, *args, **kwargs):
if not user.get("is_admin", False):
print("Доступ запрещён")
return None
return func(user, *args, **kwargs)
return wrapper
@require_admin
def delete_user(user, user_id):
print(f"Пользователь {user_id} удалён")
admin = {"name": "admin", "is_admin": True}
user = {"name": "alice", "is_admin": False}
delete_user(admin, 123) # Пользователь 123 удалён
delete_user(user, 456) # Доступ запрещёнСохранение метаданных с @wraps
Без @wraps декоратор теряет имя и документацию функции.
# Без wraps
def logger(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@logger
def greet():
"""Приветствие"""
return "Hello"
print(greet.__name__) # wrapper
print(greet.__doc__) # NoneС wraps:
from functools import wraps
def logger(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@logger
def greet():
"""Приветствие"""
return "Hello"
print(greet.__name__) # greet
print(greet.__doc__) # ПриветствиеСовет: Всегда используй
@wrapsв своих декораторах. Это хорошая практика.
Декоратор для повторных попыток (retry)
import time
def retry(times=3):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(times):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"Попытка {attempt + 1} не удалась: {e}")
time.sleep(1)
return None
return wrapper
return decorator
@retry(times=5)
def unstable_function():
import random
if random.random() < 0.7:
raise ValueError("Случайная ошибка!")
return "Успешно!"
print(unstable_function())Цепочка декораторов
@timer
@logger
def add(a, b):
return a + b
add(3, 5)
# Вывод:
# Вызов функции: add
# Результат: 8
# Функция add выполнилась за 0.0000 секВажно: Порядок декораторов важен. Снизу вверх: сначала
@logger, потом@timer.
Задачи для закрепления
Задача 1. Напиши декоратор, который выводит сообщение до и после вызова функции.
Задача 2. Напиши декоратор, который замеряет время выполнения функции.
Задача 3. Напиши декоратор, который проверяет, что пользователь является администратором.
Задача 4. Напиши декоратор, который выводит аргументы функции.
Задача 5. Напиши декоратор, который сохраняет результат функции в кэш и при повторном вызове с теми же аргументами возвращает сохранённый результат.
Ответы:
Задача 1.
def border(func):
def wrapper(*args, **kwargs):
print("Начало работы")
result = func(*args, **kwargs)
print("Конец работы")
return result
return wrapperЗадача 2.
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"Время: {end - start:.4f} сек")
return result
return wrapperЗадача 3.
def require_admin(func):
def wrapper(user, *args, **kwargs):
if not user.get("is_admin", False):
return "Доступ запрещён"
return func(user, *args, **kwargs)
return wrapperЗадача 4.
def debug(func):
def wrapper(*args, **kwargs):
print(f"Аргументы: {args}, {kwargs}")
return func(*args, **kwargs)
return wrapperЗадача 5.
def cache(func):
cache_storage = {}
def wrapper(*args, **kwargs):
key = (args, tuple(kwargs.items()))
if key not in cache_storage:
cache_storage[key] = func(*args, **kwargs)
return cache_storage[key]
return wrapper
@cache
def slow_square(x):
import time
time.sleep(1)
return x ** 2
print(slow_square(2)) # 4 (ждём 1 сек)
print(slow_square(2)) # 4 (мгновенно)Нюансы и подводные камни
Сохранение метаданных
Всегда используй @wraps для сохранения имени и документации.
Порядок декораторов
@decorator1
@decorator2
def func():
pass
# Эквивалентно
func = decorator1(decorator2(func))Декоратор без возврата функции
Если декоратор не возвращает функцию, исходная функция будет заменена на None.
Частые ошибки и как их избежать
Ошибка 1: Забыл *args и **kwargs
Всегда используй *args и **kwargs для поддержки любых аргументов.
Ошибка 2: Забыл @wraps
Используй @wraps для сохранения метаданных.
Ошибка 3: Декоратор не возвращает функцию
Всегда возвращай wrapper.
Шпаргалка
| Что нужно | Как пишется |
|---|---|
| Декоратор без аргументов | def decorator(func): ... return wrapper |
| С сохранением метаданных | from functools import wraps |
| Применение декоратора | @decorator |
| Порядок декораторов | Снизу вверх: @deco1 @deco2 |
Заключение / Выводы
Сегодня мы:
- Научились писать простейшие декораторы
- Разобрали структуру декоратора
- Познакомились с
@wraps - Решали практические задачи








