Найти в Дзене
Дойти до IT

Python - Декораторы

Декора́торы — это функции высшего порядка, которые принимают другую функцию в качестве аргумента, модифицируют её поведение и возвращают новую функцию. Основное назначение декоратора — добавить дополнительную логику вокруг вызываемой функции, не меняя её исходный код. Они так же изменяют работу не только функций, но и классов Напишем декоратор, который будет замерять время выполнения функции 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(обертка), она будет выполнять дополнительные действия перед и/или вызовом основной функцииб передадим ей
Оглавление

Декора́торы — это функции высшего порядка, которые принимают другую функцию в качестве аргумента, модифицируют её поведение и возвращают новую функцию. Основное назначение декоратора — добавить дополнительную логику вокруг вызываемой функции, не меняя её исходный код.

Они так же изменяют работу не только функций, но и классов

Напишем декоратор, который будет замерять время выполнения функции

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}!")

Здесь происходит следующее:

  1. Мы создаем декоратор log_decorator, который принимает функцию func в качестве аргумента.
  2. Внутри декоратора создается новая функция-обертка (wrapper), которая добавляет логирование перед вызовом оригинальной функции.
  3. Оригинальная функция заменяется на эту обертку благодаря использованию синтаксиса @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").