Вопрос проверяет понимание структурных паттернов и того, как в Python устроены декораторы функций/методов и расширение поведения через обёртки.
Decorator — это паттерн, который добавляет поведение объекту через «обёртку», не меняя исходный класс/функцию. В Python под «декоратором» чаще всего понимают функцию, которая принимает другую функцию и возвращает новую функцию-обёртку. Это удобно для логирования, кэширования, проверки прав, ретраев. Важно корректно прокидывать аргументы и сохранять метаданные функции.
Decorator (паттерн) — это структурный паттерн, который позволяет динамически добавлять поведение объекту, оборачивая его другим объектом с тем же интерфейсом.
В Python слово «декоратор» часто используют в более узком смысле: синтаксис @decorator для функций и методов, который подменяет исходную функцию на обёрнутую.
Обычно его применяют, когда нужно:
Добавить сквозную функциональность (cross-cutting concerns)
логирование
метрики
трейсинг
контроль доступа
Не раздувать бизнес-код повторяющимися проверками
Сохранять возможность комбинировать несколько «надстроек»
Декоратор — это вызываемый объект, который:
Принимает функцию func
Создаёт wrapper(*args, **kwargs)
Возвращает wrapper
Простой пример:
import time
def timing(func):
def wrapper(*args, **kwargs):
started = time.time()
result = func(*args, **kwargs)
elapsed = time.time() - started
print(f"{func.__name__} took {elapsed:.3f}s")
return result
return wrapper
@timing
def slow(x):
time.sleep(0.1)
return x * 2
functools.wrapsБез него теряются метаданные функции: __name__, __doc__, сигнатура становится менее понятной.
from functools import wraps
def timing(func):
@wraps(func)
def wrapper(*args, **kwargs):
# ... та же логика
return func(*args, **kwargs)
return wrapper
Это важно для:
логирования по имени функции
документации
интроспекции
некоторых фреймворков (например, роутинг/DI)
Иногда нужен декоратор с конфигурацией. Тогда появляется дополнительный уровень:
decorator_factory(config) возвращает decorator
decorator(func) возвращает wrapper
from functools import wraps
import time
def retry(times, delay_sec=0.0):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
last_exc = None
for _ in range(times):
try:
return func(*args, **kwargs)
except Exception as e:
last_exc = e
if delay_sec:
time.sleep(delay_sec)
raise last_exc
return wrapper
return decorator
@retry(times=3, delay_sec=0.1)
def fragile():
...
Паттерн «Decorator» в ООП — это когда у вас есть интерфейс и обёртка, которая реализует тот же интерфейс, но добавляет логику до/после.
Схема:
Базовый интерфейс
Реальная реализация
Декоратор хранит ссылку на объект и проксирует вызовы, добавляя поведение
Упрощённый пример:
class Storage:
def get(self, key):
raise NotImplementedError
class RedisStorage(Storage):
def get(self, key):
return "value" # условно
class LoggingStorage(Storage):
def __init__(self, storage):
self._storage = storage
def get(self, key):
print("get:", key)
return self._storage.get(key)
Не прокидывают *args, **kwargs
Не возвращают результат исходной функции
Ломают исключения (например, глотают их без необходимости)
Не используют @wraps, из-за чего страдают метрики/интроспекция
Делают слишком «тяжёлые» декораторы, которые трудно тестировать
Decorator применяют, когда нужно добавлять поведение поверх существующей логики без изменения исходного кода. В Python это естественно выражается через @decorator, а для качества кода важно корректно обрабатывать аргументы, возвращаемые значения и метаданные (functools.wraps).