Декора́торы — это функции высшего порядка, которые принимают другую функцию в качестве аргумента, модифицируют её поведение и возвращают новую функцию. Основное назначение декоратора — добавить дополнительную логику вокруг вызываемой функции, не меняя её исходный код.
Они так же изменяют работу не только функций, но и классов
Напишем декоратор, который будет замерять время выполнения функции
import time
def timer(func):
____def wrapper(*args, **kwargs):
________start_time = time.time()
________result = func(*args, **kwargs)
________end_time = time.time()
________print(f"Время выполнения: {end_time - start_time} секунд")
________return result
____return wrapper
Импортируем модуль time из стандартной библиотеки Python
Определим функцию-декоратор, назовем ее timer, а ее параметром будет другая функция
Внутри функции-декоратора определим еще одну функцию, которую обычно называют wrapper(обертка), она будет выполнять дополнительные действия перед и/или вызовом основной функцииб передадим ей args и kwargs
Засечем время перед вызовом нашей функции и после, а затем вычтем начальное время из конечного и получим время выполнения функции и вернем саму функцию
В функции-декораторе вернем нашу функцию-обертку
Применение декоратора
Замерим время выполнения медленной функции.
@timer
def slow_function():
____for i in range(1, 999999999):
________pass
____return "Готово!"
print(slow_function())
Для того чтобы обернуть нашу функцию в декоратор, нужно написать его название вместе с символом @, что эквивалентно следующему синтаксису:
slow_function = timer(slow_function)
То есть мы буквально помещаем нашу функцию в обертку другой функции, которая расширяет функционал
Благодаря декоратору мы получили время, за которое выполнилась наша функция, без дополнительного кода в теле нашей основной функции. Плюсом является то, что декоратор можно использовать неограниченное кол-во раз, и нам не придется плодить много кода.
Декоратор с аргументами
Мы так же можем написать декоратор, который будет принимать в себя аргументы. Давайте модифицируем наш декоратор таким образом, чтобы он выполнял функцию n раз, показывал сколько итераций прошло и сколько секунд понадобилось
import time
def timer(iters):
____def decorator(func):
________def wrapper(*args, **kwargs):
____________total = 0
____________for i in range(iters):
________________start_time = time.time()
________________result = func(*args, **kwargs)
________________end_time = time.time()
________________total = total + (end_time - start_time)
____________print(f"Всего итерации:{iters}\nВсего {total} секунд")
____________return result
________return wrapper
____return decorator
Здесь мы объявили функцию timer с параметром iters, который указывает, сколько раз выполниться наша функция. Заметьте, функция timer не является декоратором, это обычная функция, которая возвращает декоратор
Обернем нашу функцию в декоратор, а в скобках после декоратора укажем кол-во итераций
@timer(10)
slow_function():
____for i in range(1, 99999999):
________pass
____return "Готово!"
print(slow_function())
Ещё про декораторы
Представим себе простую функцию:
def greet(name):
____print(f"Hello, {name}!")
Теперь мы хотим добавить к этой функции возможность записи лога каждый раз, когда она вызывается. Для этого можно воспользоваться декоратором:
def log_decorator(func):
____def wrapper(*args, **kwargs):
________print(f"Function '{func.__name__}' called with args: {args}, kwargs: {kwargs}")
________return func(*args, **kwargs)
____return wrapper
@log_decorator
def greet(name):
____print(f"Hello, {name}!")
Здесь происходит следующее:
- Мы создаем декоратор log_decorator, который принимает функцию func в качестве аргумента.
- Внутри декоратора создается новая функция-обертка (wrapper), которая добавляет логирование перед вызовом оригинальной функции.
- Оригинальная функция заменяется на эту обертку благодаря использованию синтаксиса @log_decorator перед определением функции.
Пример работы:
Если теперь вызвать функцию greet("Alice"), будет выведено:
Function 'greet' called with args: ('Alice',), kwargs: {}
Hello, Alice!
Зачем нужны декораторы?
Декораторы позволяют гибко добавлять повторяющиеся куски кода, такие как:
- Логирование.
- Проверки условий.
- Таймеры выполнения.
- Обработку исключений.
- Кэширование результатов.
Без декораторов пришлось бы дублировать этот код в каждой функции, что усложняет сопровождение программы.
Важные моменты:
- Декораторы могут принимать аргументы (например, параметры для настройки логирования).
- Можно применять несколько декораторов к одной функции.
- Порядок применения декораторов важен: они применяются снизу вверх.
Вот пример применения нескольких декораторов:
def timer_decorator(func):
____import time
____def wrapper(*args, **kwargs):
________start_time = time.time()
________result = func(*args, **kwargs)
________end_time = time.time()
________print(f"Function '{func.__name__}' executed in {end_time - start_time:.2f} seconds")
________return result
____return wrapper
@timer_decorator
@log_decorator
def greet(name):
____print(f"Hello, {name}!")
Теперь при вызове greet("Bob") будут выполнены обе операции: логирование и измерение времени выполнения.Таким образом, декораторы — это мощный инструмент для модификации поведения функций без изменения их исходного кода, что делает программирование более модульным и поддерживает принцип DRY ("Don't Repeat Yourself").