fbpx

Зачем нужны декораторы Python?

04.08.2023

Кто такие декораторы в Python?

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

Этот механизм применяется с помощью символа «@» перед определением целевой функции. Когда вызывается декорированная функция, она фактически передается в декоратор, который может выполнять дополнительный код до и после вызова целевой, изменять ее аргументы или результаты, а также выполнять другие операции, необходимые для дополнительной обработки.

Примером может служить декоратор для измерения времени выполнения функции:

При вызове some_function() вместо нее будет выполнена функция wrapper, добавляющая функционал измерения времени.

Подробнее о декораторе в Python

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

Схема объявления декоратора в Python:

decorator_function – это основная функция декоратора, которая принимает целевую target_function в качестве аргумента.

wrapper_function – внутренняя функция, также называемая оберткой (wrapper), которая представляет собой новую функцию, возвращаемую декоратором. Она выполняет дополнительную логику до и после вызова target_function.

*args и **kwargs – это параметры оберточной функции, которые позволяют принимать произвольное количество позиционных и именованных аргументов, передаваемых в target_function.

Чтобы применить декоратор к целевой функции, используется символ «@» перед определением целевой функции. Например:

В этом примере my_decorator – это декоратор, а my_function – это целевая функция. При вызове my_function() с примененным декоратором будет выведено:

Таким образом, my_decorator добавил дополнительную функциональность к my_function, выполнив логику до и после её вызова.

В контексте декораторов в Python термин «prime» обычно используется для обозначения декоратора, который применяется к функции или методу класса без передачи дополнительных аргументов или параметров. Иными словами, «prime» декораторы не принимают аргументы или параметры, а просто применяются с помощью символа @ перед определением целевой функции.

Как работают декораторы?

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

Рассмотрим, как работают декораторы на примере функционального декоратора:

Сначала определяется функция декоратора. Она принимает одну аргумент – целевую функцию, которую нужно декорировать. Внутри декоратора определяется оберточная функция (wrapper), которая выполняет дополнительную логику до и после вызова целевой.

Для применения декоратора к целевой функции используется символ @ перед определением функции.

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

Например:

При вызове my_function() происходит следующее:

  • @my_decorator применяет декоратор my_decorator к функции Эквивалентно выполнению my_function = my_decorator(my_function).
  • Когда вызывается my_function(), вместо неё будет вызвана wrapper. Выведется «До вызова функции».
  • wrapper вызывает target_function, которой на самом деле является исходная my_function.
  • Исходная my_function выполняется и выводит «Функция выполняется».
  • После выполнения target_function (т. е., my_function) в wrapper выводится «После вызова функции».
  • Результат выполнения my_function (если он есть) возвращается в точку вызова, и выводится результат или возвращаемое значение.

Мы видим, что my_decorator добавил дополнительную функциональность к my_function, выполнив логику до и после её вызова без изменения самой my_function.

90% клиентов пришли к нам по рекомендации

Декораторы функций

Рассмотрим поподробнее декораторы функций retry, timer и functools.wraps.

  • retry позволяет повторно вызывать функцию. Это может быть полезно в ситуациях, когда функция может временно не выполняться из-за внешних факторов, и нужно дать ей повторные попытки для успешного выполнения. Декоратор retry позволяет установить максимальное количество попыток и интервал между ними.

Например:

import time

def retry(max_attempts, delay):

def decorator_function(target_function):

def wrapper(*args, **kwargs):

for attempt in range(max_attempts):

try:

result = target_function(*args, **kwargs)

except Exception as e:

print(f»Попытка {attempt+1} завершилась ошибкой: {e}»)

time.sleep(delay)

else:

return result

raise Exception(f»Превышено максимальное количество попыток ({max_attempts}).»)

return wrapper

return decorator_function

@retry(max_attempts=3, delay=1)

def unstable_function():

import random

if random.random() < 0.5:

raise ValueError(«Неудачная попытка»)

return «Успешный результат»

result = unstable_function()

print(result)

  • timer используется для измерения времени выполнения функции. Это полезно, когда нужно оптимизировать производительность кода. Декоратор timer печатает время, затраченное на выполнение функции.

Пример:

import time

def timer(target_function):

def wrapper(*args, **kwargs):

start_time = time.time()

result = target_function(*args, **kwargs)

end_time = time.time()

execution_time = end_time — start_time

print(f»Время выполнения функции {target_function.__name__}: {execution_time:.5f} секунд.»)

return result

return wrapper

@timer

def slow_function():

time.sleep(2)

return «Завершено»

result = slow_function()

print(result)

  • wraps является вспомогательным декоратором, используемым внутри других декораторов, чтобы сохранить метаинформацию и атрибуты исходной функции. Это важно, потому что оберточные функции декораторов могут изменять некоторые свойства исходной функции, и functools.wraps помогает сохранить исходные атрибуты функции.

Пример:

В этом примере functools.wraps используется для сохранения имени исходной функции my_function, в результате чего my_function.__name__ выводит ‘my_function’, а не ‘wrapper’.

Все эти декораторы представляют собой мощные инструменты, которые позволяют легко добавлять функциональность и модифицировать поведение функций в Python, делая код более удобным и эффективным.

Области видимости и замыкания

Это важные концепции в Python, которые касаются области действия переменных и их доступности в различных частях кода. Понимание этих концепций позволяет более глубоко понять, как работает данный язык.

  1. Области видимости (Scope) определяют контекст, в котором переменная доступна или видима.

В Python существует три основных типа областей видимости:

  • Глобальная область видимости (Global scope). Переменные, определенные в глобальной области видимости, доступны из любого места в коде. Они объявляются вне функций или классов или с помощью ключевого слова global внутри функций.
  • Локальная область видимости (Local scope). Переменные, объявленные внутри функций, имеют локальную область видимости и существуют только в пределах этой функции. Они недоступны за ее пределами.
  • Встроенная область видимости (Built-in scope). В Python есть встроенные функции и имена, которые доступны из любой точки программы. Например, функции print() и len() являются встроенными и могут использоваться в любой части кода.

Например:

global_var = 10  # Глобальная область видимости

def example_function():

local_var = 20  # Локальная область видимости

print(global_var)  # Можно обращаться к глобальной переменной внутри функции

example_function()

# print(local_var)  # Ошибка, так как локальная переменная недоступна за пределами функции

  1. Замыкания (Closures) – это функции, которые запоминают значения переменных из области видимости, в которой они были созданы. Они возникают, когда функция определена внутри другой функции, и внутренняя ссылается на переменные из внешней. Это позволяет сохранять состояние между вызовами внутренней функции и делает замыкания мощным инструментом для реализации функциональных идиом и обеспечения безопасности данных.

Например:

def outer_function(x):

def inner_function(y):

return x + y

return inner_function

closure_example = outer_function(10)

result = closure_example(5)  # Вызов замыкания

print(result)  # Выведет 15, так как внутренняя функция запомнила значение x=10 из внешней функции

Здесь outer_function возвращает inner_function, и внутренняя функция «запоминает» значение переменной x из внешней функции, что позволяет использовать это значение при последующих вызовах внутренней функции.

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

Можно использовать декораторы для добавления дополнительного функционала к функциям. Например, @timer_decorator добавляет измерение времени выполнения функции.

Хотя это не рекомендуется из-за проблем с чистотой кода, декораторы могут быть использованы для создания глобальных переменных внутри функций. Например, этот механизм может использоваться для присвоения значения функции глобальной переменной.

Декораторы, создающие замыкания, – это одно из наиболее распространенных применений декораторов в Python. Они могут быть использованы для, например, реализации карринга. Карринг – это техника преобразования функции с несколькими аргументами в последовательность функций с одним аргументом. Декоратор может превратить обычную функцию в каррированную, позволяя ей принимать аргументы частично, до тех пор, пока все необходимые данные не будут предоставлены. Кроме того, замыкания позволяют функции запоминать состояние из внешней области видимости. Декораторы могут использоваться для создания замыканий, которые сохраняют состояние между вызовами функции. Таким образом, функция может «запоминать» значения переменных.

Пример использования декоратора для создания замыкания:

def add_to_number(x):

def add(y):

return x + y

return add

add_five = add_to_number(5)

result = add_five(3)  # Вызов замыкания

print(result)  # Выведет 8, так как функция «запомнила» значение x=5 из внешней функции

В данном примере декоратор add_to_number превращает функцию add в замыкание, захватывая значение переменной x из внешней области видимости функции add_to_number. После этого add_five становится функцией, которая «запоминает» значение x=5, и добавляет это значение к переданному аргументу.

Среднее время реакции на обращение: 13,5 мин.
Среднее время решения задачи: 1 час 21 мин.

Продвинутые декораторы

Продвинутые декораторы в Python предоставляют возможность создавать более гибкие и мощные декораторы, которые могут принимать аргументы и/или параметры, а также использовать классы вместо функций для определения декораторов. Рассмотрим каждый из этих подходов.

Обычные декораторы принимают только одну функцию в качестве аргумента. Однако, если нам нужно передать дополнительные аргументы в декоратор, чтобы изменить его поведение, мы можем создать такой механизм, который сам принимает аргументы и возвращает функцию-обертку. Возможные применения декораторов с аргументами – это, например, настройка функции с разными параметрами или добавление различного поведения в зависимости от переданных аргументов.

Например:

В этом примере greeting_decorator принимает аргумент greeting_message, который будет выводиться перед вызовом целевой функции. При вызове my_function() с декоратором будет выведено:

 

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

Например:

 

В этом примере repeat_decorator принимает параметр repeats, который определяет, сколько раз будет вызвана целевая функция. При вызове say_hello() с декоратором, функция «Привет!» будет вызвана три раза.

В Python декораторы могут быть реализованы не только с помощью функций, но и с использованием классов. Для создания класса-декоратора мы должны определить методы __init__ и __call__. Метод __init__ выполняет инициализацию декоратора и принимает аргументы, если они необходимы. __call__ предоставляет функциональность, которая будет выполняться при вызове декорированной функции.

Пример:

Этот пример реализует класс GreetingDecorator, который может быть использован как декоратор. Когда my_function() вызывается с применением класса-декоратора, результат будет таким же, как и в первом примере с функциональным декоратором.

Продвинутые декораторы расширяют возможности базовых декораторов, делая их более гибкими и удобными для разнообразных задач.

Где используются?

  • Декораторы часто используются для реализации логирования, чтобы записывать информацию о вызовах функций, аргументах, возвращаемых значениях и времени выполнения.
  • Могут использоваться для реализации механизмов кэширования результатов выполнения функций, чтобы избежать повторных вычислений.
  • Позволяют добавлять проверку аутентификации или авторизации перед выполнением функции.
  • Могут быть применены для валидации аргументов функций, чтобы проверить, что переданные значения соответствуют ожидаемым.
  • Используются для регистрации функций в общем реестре или реализации плагинов.
  • Позволяют добавить обработку исключений в функции, чтобы перехватывать и обрабатывать ошибки.
  • Декораторы используются для измерения времени выполнения функций, что полезно при оптимизации кода.
  • Могут применяться для превращения функций с несколькими аргументами в функции с одним аргументом, чтобы облегчить их частичное применение.
  • Позволяют ограничивать доступ к определенным функциям или методам класса в зависимости от роли пользователя.
  • Могут использоваться для сбора статистики и мониторинга производительности системы.

Кроме того, эти инструменты широко используются во фреймворках и библиотеках Python для добавления дополнительной функциональности, расширения и кастомизации. Например, веб-фреймворки Flask и Django используют декораторы для определения маршрутов и представлений (views). Библиотека SQLAlchemy использует их для описания моделей базы данных.

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

Резюме

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

Представьте, у нас есть класс, который содержит несколько методов для работы с данными, и мы хотим логировать каждый вызов этих методов. В этом случае, мы можем использовать декоратор, чтобы автоматически добавить функциональность логирования ко всем методам этого класса.

Примерно так это может выглядеть:

def logged(func):
def wrapper(*args, **kwargs):
print(f"Вызов функции {func.__name__} с аргументами {args} и ключевыми аргументами {kwargs}")
result = func(*args, **kwargs)
print(f"Функция {func.__name__} вернула результат: {result}")
return result
return wrapper

class MyClass:
@logged
def my_method(self, x, y):
return x + y

obj = MyClass()
obj.my_method(2, 3)

В этом примере, декоратор logged принимает функцию func в качестве аргумента и возвращает новую функцию wrapper, которая добавляет логирование вокруг вызова func. При вызове метода my_method объекта obj, будет выводиться информация о вызове метода и его результате.

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

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

Так что, знание и понимание декораторов в Python может существенно обогатить ваш набор инструментов в программировании и помочь в более эффективной работе с объектами и функциями.

Остались вопросы?

Оставьте заявку и наш менеджер свяжется с Вами в течение 15 минут