
Декораторы в Python позволяют модифицировать поведение функций без изменения их исходного кода. Они реализуются как функции или классы, принимающие на вход другую функцию и возвращающие новую функцию с добавленной функциональностью. Такой подход ускоряет повторное использование кода и упрощает сопровождение проектов.
Частые сценарии применения декораторов включают логирование вызовов, проверку прав доступа, измерение времени выполнения и кэширование результатов. Например, декоратор @lru_cache из стандартной библиотеки functools позволяет автоматически сохранять результаты функций, сокращая время вычислений при повторных вызовах с одинаковыми аргументами.
Создание собственного декоратора начинается с определения функции, принимающей функцию-аргумент. Для сохранения метаданных исходной функции рекомендуется использовать functools.wraps. Такой подход предотвращает потерю информации о документации и имени функции, что важно при работе с большими проектами и инструментами отладки.
Использование декораторов в связке с аргументами расширяет возможности модификации функций. Они позволяют передавать параметры в декоратор, например, для настройки уровня логирования или условия кэширования. Это делает декораторы гибким инструментом, который повышает читаемость и масштабируемость кода.
Как добавить логирование вызовов функции с помощью декоратора

Для логирования вызовов функции создайте декоратор, который оборачивает исходную функцию, фиксируя аргументы, результат и время выполнения. Используйте модуль functools для сохранения метаданных оригинальной функции.
Пример декоратора:
import functools
def log_calls(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f'Вызов {func.__name__} с args={args}, kwargs={kwargs}')
result = func(*args, **kwargs)
print(f'{func.__name__} вернула {result}')
return result
return wrapper
Применение:
@log_calls
def multiply(a, b):
return a * b
multiply(3, 5)
Для измерения времени выполнения добавьте import time и фиксируйте start = time.time() перед вызовом функции, а end = time.time() после. Разница end - start даст точное время работы функции.
Декоратор можно расширять: логировать исключения, сохранять стек вызовов, передавать формат сообщений и настраивать уровни логирования через параметры декоратора.
Создание декоратора для измерения времени выполнения функции
Декоратор для измерения времени выполнения функции позволяет точно оценить производительность отдельных блоков кода и выявить узкие места. Для его реализации рекомендуется использовать модуль time.
Пример базового декоратора:
import time
def timer(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
Особенности реализации:
- Используется
time.perf_counter()для высокой точности измерений. - Аргументы функции передаются через
*argsиkwargsдля сохранения универсальности декоратора. - Возвращается результат функции для корректного использования в дальнейшем коде.
Пример применения:
@timer
def calculate():
total = 0
for i in range(106):
total += i
return total
calculate()
Рекомендации для практического использования:
- Для многократного тестирования оборачивайте функции в цикл, чтобы получить среднее время выполнения.
- В больших проектах декоратор можно расширить логированием в файл вместо печати на экран.
- При измерении времени асинхронных функций используйте
async defиawaitвнутри декоратора.
Такой декоратор позволяет быстро выявить узкие места в алгоритмах и оптимизировать критические участки кода без изменения основной логики функции.
Использование декораторов для проверки аргументов функции

Декораторы в Python позволяют централизованно проверять входные данные функции, что снижает дублирование кода и повышает надежность. Простейший пример – проверка типов аргументов:
def type_check(expected_type):
def decorator(func):
def wrapper(arg):
if not isinstance(arg, expected_type):
raise TypeError(f"Ожидается {expected_type}, получено {type(arg)}")
return func(arg)
return wrapper
return decorator
Применение:
@type_check(int)
def factorial(n):
if n == 0: return 1
return n * factorial(n - 1)
Декоратор остановит выполнение при передаче некорректного типа, предотвращая скрытые ошибки.
Для проверки диапазона значений аргументов используется следующий подход:
def range_check(min_value, max_value):
def decorator(func):
def wrapper(arg):
if not (min_value <= arg <= max_value):
raise ValueError(f"Аргумент {arg} вне диапазона [{min_value}, {max_value}]")
return func(arg)
return wrapper
return decorator
Пример использования:
@range_check(1, 100)
def set_percentage(value):
return f"Установлено {value}%"
Для проверки нескольких аргументов удобно использовать таблицу правил:
| Аргумент | Тип | Диапазон |
|---|---|---|
| age | int | 0–120 |
| salary | float | 0–1_000_000 |
| name | str | не пустая строка |
Соответствующий декоратор может динамически проверять каждое правило:
def validate_args(rules):
def decorator(func):
def wrapper(kwargs):
for arg, rule in rules.items():
value = kwargs.get(arg)
if not isinstance(value, rule['type']):
raise TypeError(f"{arg}: ожидается {rule['type'].__name__}")
if 'range' in rule and not (rule['range'][0] <= value <= rule['range'][1]):
raise ValueError(f"{arg}: значение вне диапазона {rule['range']}")
return func(kwargs)
return wrapper
return decorator
Такой подход упрощает поддержку функций с большим количеством аргументов и исключает дублирование проверок в теле функций.
Применение декораторов для кэширования результатов функций

Декораторы позволяют сохранять результаты вызова функций и повторно использовать их без повторного выполнения вычислений. В Python для этого часто используют модуль functools с декоратором @lru_cache, который реализует кэш с ограничением по числу хранимых результатов.
Пример применения @lru_cache для функции вычисления чисел Фибоначчи:
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
Здесь maxsize ограничивает количество кэшированных значений. При повторном вызове с теми же аргументами результат берется из кэша, что сокращает время выполнения сложных рекурсивных функций.
Для функций с изменяемыми аргументами или сложными структурами данных используют кастомные декораторы с хранением кэша в словаре. Важно учитывать размер кэша и возможность утечек памяти при больших объемах данных.
Пример собственного кэш-декоратора:
def cache_results(func):
cache = {}
def wrapper(*args):
if args in cache:
return cache[args]
result = func(*args)
cache[args] = result
return result
return wrapper
@cache_results
def compute_expensive(x, y):
return x y + y x
Использование кэширования через декораторы эффективно для функций с повторяющимися вызовами и неизменяемыми входными данными. Рекомендуется анализировать объем памяти и выбирать стратегию хранения результатов: LRU, полный кэш или с ограничением по времени жизни.
Для асинхронных функций можно создавать отдельные декораторы с поддержкой async, чтобы кэширование не блокировало цикл событий.
Практическая рекомендация: при внедрении кэширования проверяйте, что кэшируемые данные не изменяются внешними процессами, иначе результаты могут быть некорректными.
Модификация поведения функции с помощью вложенных декораторов

Вложенные декораторы позволяют последовательно изменять поведение функции, накладывая несколько слоев логики. Каждый декоратор оборачивает функцию, получая на вход результат предыдущего. Порядок применения критически важен: первый в списке @ выполняется последним.
Пример использования:
def uppercase(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs).upper()
return wrapper
def add_exclamation(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs) + '!'
return wrapper
@add_exclamation
@uppercase
def greet(name):
return f"Hello, {name}"
В таблице ниже показано влияние порядка вложения декораторов:
| Порядок декораторов | Результат вызова greet("Alice") |
|---|---|
| @add_exclamation @uppercase |
HELLO, ALICE! |
| @uppercase @add_exclamation |
HELLO, ALICE! |
При работе с вложенными декораторами рекомендуется:
- Использовать functools.wraps для сохранения метаданных функции.
- Проверять совместимость типов возвращаемых значений между декораторами.
- Разделять декораторы на атомарные задачи, чтобы можно было гибко менять порядок без ошибок.
Вложенные декораторы особенно эффективны для логирования, контроля доступа и трансформации данных до передачи функции, обеспечивая модульность и повторное использование кода.
Создание декоратора для обработки исключений внутри функции
Декоратор для обработки исключений позволяет централизованно управлять ошибками функций, избегая повторного написания блоков try-except в каждом методе. Такой подход упрощает поддержку и повышает читаемость кода.
Пример реализации базового декоратора:
def exception_handler(func):
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"Ошибка в функции {func.__name__}: {e}")
return None
return wrapper
Применение декоратора к функции происходит через синтаксис @exception_handler. Это автоматически оборачивает вызовы функции в блок try-except без изменения её внутренней логики.
Можно расширять функциональность, передавая в декоратор типы обрабатываемых исключений или функцию логирования:
def exception_handler(exceptions=(Exception,), logger=print):
def decorator(func):
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except exceptions as e:
logger(f"Ошибка в {func.__name__}: {e}")
return None
return wrapper
return decorator
Рекомендуется сохранять оригинальное имя функции и документацию, используя functools.wraps:
from functools import wraps
def exception_handler(exceptions=(Exception,), logger=print):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except exceptions as e:
logger(f"Ошибка в {func.name}: {e}")
return None
return wrapper
return decorator
Это гарантирует корректное поведение introspection и совместимость с инструментами документации и тестирования.
Передача параметров в декораторы для настройки функций

Декораторы в Python могут принимать параметры, что позволяет гибко настраивать поведение функций без изменения их исходного кода. Для этого создают вложенные функции: внешняя принимает параметры декоратора, внутренняя – целевую функцию.
Пример передачи параметра в декоратор для логирования вызовов функции с разным уровнем детализации:
def logger(level):
def decorator(func):
def wrapper(*args, **kwargs):
if level == 'debug':
print(f"[DEBUG] Вызов функции {func.__name__} с аргументами {args} {kwargs}")
result = func(*args, **kwargs)
if level == 'debug':
print(f"[DEBUG] Результат: {result}")
return result
return wrapper
return decorator
@logger(level='debug')
def multiply(a, b):
return a * b
multiply(3, 4)
Рекомендации по работе с параметризованными декораторами:
- Используйте functools.wraps для сохранения имени и документации исходной функции:
from functools import wraps
def decorator_with_wraps(param):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
# логика декоратора
return func(*args, **kwargs)
return wrapper
return decorator
Параметризованные декораторы повышают повторное использование кода, делают функции более настраиваемыми и сокращают дублирование при изменении поведения без изменения основного тела функции.
Применение декораторов для контроля доступа к функциям
Декораторы позволяют внедрять проверку прав пользователя до выполнения основной логики функции. Это особенно полезно в веб-приложениях и API, где доступ к методам должен быть ограничен на основе ролей или токенов.
Простейший пример – декоратор, проверяющий роль пользователя:
Пример:
def require_role(role):
def decorator(func):
def wrapper(user, *args, **kwargs):
if role not in user.roles:
raise PermissionError(f'Доступ запрещен для роли {role}')
return func(user, *args, **kwargs)
return wrapper
return decorator
Использование:
@require_role('admin')
def delete_user(user, user_id):
# удаление пользователя
pass
Здесь user – объект с атрибутом roles. Декоратор проверяет наличие роли и предотвращает выполнение функции при недостаточных правах.
Для динамических приложений рекомендуется комбинировать декораторы с логированием попыток доступа. Это позволяет отслеживать несанкционированные вызовы и выявлять уязвимости.
Можно создавать универсальные декораторы, принимающие несколько допустимых ролей:
def require_roles(*roles):
def decorator(func):
def wrapper(user, *args, **kwargs):
if not any(r in user.roles for r in roles):
raise PermissionError(f'Доступ разрешен только для ролей: {roles}')
return func(user, *args, **kwargs)
return wrapper
return decorator
Такой подход сокращает дублирование кода и повышает читаемость. Рекомендуется применять строгую типизацию объектов пользователя и фиксированные наборы ролей для предотвращения ошибок при проверках.
Декораторы для контроля доступа также могут работать с токенами и сессиями, проверяя их актуальность и срок действия, что критично для REST API и микросервисной архитектуры.
Внедрение таких декораторов обеспечивает безопасный и прозрачный механизм контроля доступа без изменения бизнес-логики функций.
Вопрос-ответ:
Что такое декоратор в Python и как он работает?
Декоратор — это функция, которая принимает другую функцию и возвращает модифицированную версию этой функции или новую функцию с дополнительным поведением. Он позволяет добавлять функционал без изменения исходного кода функции. Например, с помощью декоратора можно логировать вызовы функций или проверять права доступа перед выполнением.
Как создать декоратор, который принимает аргументы?
Для создания декоратора с аргументами нужна дополнительная вложенная функция. Сначала создаётся функция-обёртка, которая принимает аргументы декоратора, а затем возвращает сам декоратор, который оборачивает целевую функцию. Такой подход позволяет гибко настраивать поведение декоратора для разных случаев использования.
Можно ли применять несколько декораторов к одной функции?
Да, в Python можно накладывать несколько декораторов на одну функцию. Они применяются сверху вниз: сначала выполняется декоратор, ближайший к определению функции, затем следующий сверху и так далее. Это позволяет комбинировать разные типы модификаций, например, логирование и проверку прав доступа одновременно.
Влияют ли декораторы на производительность программы?
Декораторы добавляют дополнительный слой вызова функции, поэтому в некоторых случаях может быть небольшое замедление. Обычно это незаметно для большинства приложений, но при использовании большого числа сложных декораторов в критических участках кода стоит оценить влияние на скорость и оптимизировать обёртки при необходимости.
Как использовать стандартные декораторы Python, например @staticmethod и @classmethod?
Декораторы @staticmethod и @classmethod применяются к методам классов для изменения их поведения. @staticmethod делает метод независимым от экземпляра класса, он не получает self. @classmethod передаёт классу сам класс через параметр cls, что позволяет создавать альтернативные конструкторы и управлять состоянием класса. Их использование упрощает структуру кода и делает методы более универсальными.
