Добавить в корзинуПозвонить
Найти в Дзене
Код как искусство

Декораторы в Python: как обернуть функцию в функциональность, не трогая её код

Вы написали полезную функцию. Всё работает. Но вдруг понадобилось замерять время её выполнения. Или логировать аргументы. Или проверять права доступа. Переписывать каждую функцию? Нет. В Python есть элегантный механизм — декораторы. Это функции, которые принимают другую функцию и возвращают её улучшенную версию. Представьте, что вы кладёте функцию в «обёртку», которая добавляет нужное поведение до или после вызова. Сегодня разберём: В Python функции — полноценные объекты. Их можно присваивать переменным, передавать в другие функции и возвращать из функций. def say_hello():
print("Привет!")
greet = say_hello # присваиваем
greet() # Привет! Это свойство — основа декораторов. Декоратор — это функция, которая принимает функцию и возвращает новую функцию (обычно через def внутри). def my_decorator(func):
def wrapper():
print("Что-то делаем ДО")
func()
print("Что-то делаем ПОСЛЕ")
return wrapper
def say_hello():
print("Привет!")
say_he
Оглавление

Вы написали полезную функцию. Всё работает. Но вдруг понадобилось замерять время её выполнения. Или логировать аргументы. Или проверять права доступа. Переписывать каждую функцию? Нет. В Python есть элегантный механизм — декораторы. Это функции, которые принимают другую функцию и возвращают её улучшенную версию. Представьте, что вы кладёте функцию в «обёртку», которая добавляет нужное поведение до или после вызова.

Сегодня разберём:

  • что такое декораторы и как они работают,
  • как написать свой первый декоратор,
  • декораторы с аргументами,
  • как применять несколько декораторов сразу,
  • реальные примеры (таймер, логгер, проверка авторизации).

1. Функции — это объекты

В Python функции — полноценные объекты. Их можно присваивать переменным, передавать в другие функции и возвращать из функций.

def say_hello():
print("Привет!")

greet = say_hello # присваиваем
greet() # Привет!

Это свойство — основа декораторов.

2. Первый декоратор: обёртка без магии

Декоратор — это функция, которая принимает функцию и возвращает новую функцию (обычно через def внутри).

def my_decorator(func):
def wrapper():
print("Что-то делаем ДО")
func()
print("Что-то делаем ПОСЛЕ")
return wrapper

def say_hello():
print("Привет!")

say_hello = my_decorator(say_hello)
say_hello()

Вывод:

Что-то делаем ДО
Привет!
Что-то делаем ПОСЛЕ

Синтаксический сахар: вместо say_hello = my_decorator(say_hello) используют символ @:

@my_decorator
def say_hello():
print("Привет!")

say_hello() # результат тот же

@my_decorator — это просто удобная запись.

3. Декоратор для функций с аргументами

Если наша функция принимает аргументы, нужно, чтобы wrapper тоже их принимал и передавал дальше. Используем *args и **kwargs.

def debug(func):
def wrapper(*args, **kwargs):
print(f"Вызов {func.__name__} с аргументами: {args}, {kwargs}")
result = func(*args, **kwargs)
print(f"Результат: {result}")
return result
return wrapper

@debug
def add(a, b):
return a + b

add(3, 5)

Вывод:

Вызов add с аргументами: (3, 5), {}
Результат: 8

4. Декораторы, которые возвращают значение

Важно: wrapper должен возвращать то, что возвращает исходная функция. Иначе вы потеряете результат.

def preserve_result(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result # обязательно
return wrapper

5. Декораторы с параметрами (декораторы высшего порядка)

Иногда нужно передать декоратору аргументы — например, количество повторений или уровень логирования. Тогда создаётся трёхуровневая вложенность:

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

@repeat(3)
def say_hello():
print("Привет!")

say_hello() # напечатает "Привет!" три раза

Синтаксис: сначала вызывается repeat(3), она возвращает настоящий декоратор decorator, который применяется к say_hello.

6. Сохранение метаданных функции (functools.wraps)

Когда вы оборачиваете функцию, её имя и документация заменяются на имя и документацию wrapper. Это мешает отладке. Исправляется через @wraps из модуля functools.

from functools import wraps

def my_decorator(func):
@wraps(func) # копирует имя, докстринг и т.д.
def wrapper(*args, **kwargs):
"""Обёртка"""
return func(*args, **kwargs)
return wrapper

@my_decorator
def say_hello():
"""Приветственная функция"""
print("Привет!")

print(say_hello.__name__) # say_hello (а не wrapper)
print(say_hello.__doc__) # Приветственная функция

7. Несколько декораторов на одну функцию

Можно навесить несколько декораторов. Они применяются снизу вверх (ближайший к функции выполняется первым).

@decorator_a
@decorator_b
def func():
pass
# Эквивалентно: func = decorator_a(decorator_b(func))

8. Реальные примеры

Таймер для измерения времени

import time
from functools import wraps

def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
end = time.perf_counter()
print(f"{func.__name__} заняла {end - start:.6f} сек")
return result
return wrapper

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

slow_sum(1000000)

Логирование вызовов в файл

def log_to_file(filename):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
with open(filename, "a", encoding="utf-8") as f:
f.write(f"Вызвана {func.__name__} с {args}, {kwargs}\n")
return func(*args, **kwargs)
return wrapper
return decorator

@log_to_file("log.txt")
def multiply(a, b):
return a * b

Проверка типов аргументов

def type_check(expected_types):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for (arg, expected) in zip(args, expected_types):
if not isinstance(arg, expected):
raise TypeError(f"Аргумент {arg} должен быть {expected}")
return func(*args, **kwargs)
return wrapper
return decorator

@type_check((int, int))
def add(a, b):
return a + b

add(3, "5") # TypeError

9. Типичные ошибки новичков

❌ Забыли return в wrapper

Функция возвращает None вместо ожидаемого результата.

❌ Забыли *args, **kwargs

Декоратор работает только с функциями без аргументов — при попытке передать аргументы получите ошибку.

❌ Не использовали @wraps

Усложняется отладка, теряется имя функции и документация.

❌ Путаница с порядком при нескольких декораторах

Помните: ближайший к функции применяется первым.

Заключение

Декораторы — это элегантный способ расширять поведение функций без изменения их исходного кода. Вы узнали:

  • как создавать простые и параметризованные декораторы,
  • как сохранять метаданные через functools.wraps,
  • как применять несколько декораторов,
  • увидели реальные примеры (таймер, логгер, проверка типов).

Теперь ваш код может быть и красивым, и функциональным. Следующая статья будет о тестировании — как убедиться, что ваш код работает правильно, и не бояться его менять. А пока — попробуйте написать декоратор, который перехватывает и обрабатывает исключения, чтобы программа не падала.

Делитесь в комментариях, какой декоратор вы написали первым и где он пригодился.

Статья подготовлена для канала «Код как искусство». Подписывайтесь, чтобы писать код, который обрастает полезными обёртками, а не костылями.