Python: Функции
Теория: Декораторы
В программировании часто возникают задачи, когда нужно добавить поведение к уже существующим функциям или классам. Например, логирование, проверку входных данных или замер времени выполнения функции. В таких случаях использование декораторов может значительно упростить решение задачи.
В этом уроке мы рассмотрим, что такое декораторы и как их использовать, чтобы добавлять дополнительную функциональность к существующим функциям.
Что такое декораторы
Ранее мы рассматривали как можно "запомнить" передаваемые в функцию значения с помощью замыканий. Напомним как выглядит синтаксис:
В этом примере мы создаем функцию outer(), которая принимает аргумент arg1 и возвращает внутреннюю функцию inner(). Внутренняя функция принимает аргумент arg2 и возвращает сумму arg1 и agr2.
Может запутать одновременное определение функции и возврат ее же. Но если мы вспомним, что определение функции это присваивание функции имени переменной, а также, что функции это данные, то пример можно представить как:
Мы заменили именованную функцию лямбдой, и теперь присваивание ничем не отличается, как если бы мы присвоили переменной inner число или строку.
Но если функции всего лишь данные, то можно ли их также "запоминать" в замыканиях?
Остановимся и разберем пример выше. В нем мы создаем замыкание, но теперь, вместо числа в замыкание передается функция. Внешняя функция запомнила переданную в нее функцию. Осталось лишь передать аргументы и вызвать замкнутую функцию с ними. В дополнение, мы смогли распечатать переданную функцию и ее аргументы, при этом код самой функции square() мы не меняли.
Функции выше называются декораторами. Декораторы в Python — это функции, которые принимают другую функцию в качестве аргумента, добавляют к ней дополнительную функциональность и возвращают функцию с измененным поведением. Декораторы позволяют изменять поведение функций и классов с помощью добавления или изменения их функциональности без изменения самого кода.
Также, как вы могли заметить, декораторы это частный случай функции высшего порядка (принимаем функцию) и использования замыкания вместе.
Использование декораторов
Декораторы используются в Python повсеместно: для логирования, кеширования, добавления нового функционала, используются в тестовых и веб-фреймворках.
Предположим, что у нас есть функция, которая суммирует числа:
Создадим декоратор, который добавит к этой функции функциональность для отладки:
Этот декоратор принимает функцию в качестве аргумента и возвращает новую функцию-обертку, которая добавляет отладочные сообщения в процесс выполнения исходной функции. Заметьте, что внутренняя функция принимает сразу все аргументы с помощью *args и **kwargs. Так мы можем создавать "всеядные" обертки.
Применим этот декоратор к функции sum():
Проблема в коде выше, что теперь функция называется debugged_sum(), и нужно будет изменить весь код, что использовал sum(). Но ведь имя функции это всего лишь имя переменной, и мы можем перезаписать новую, декорированную функцию в ту же переменную.
Теперь функция sum() будет выполняться с дополнительными отладочными сообщениями. А также весь код, использующий функцию sum() будет использовать новую функцию.
Чтобы не добавлять запись вида func = decorator(func) каждый раз, в Python добавили синтаксический сахар - @decorator
Также мы можем создавать несколько декораторов для одной функции, которые будут применяться последовательно:
В этом примере сначала применится декоратор времени выполнения, а затем отладочный декоратор.
Состояние
Отличительная черта функций, что они не хранят состояния. Функции, мы говорим о чистых функциях, лишь преобразуют входные данные в выходные. Или как еще говорят, "отображают" входное множество на выходное.
Но замыкания позволяют запоминать значения и использовать их в последующих вычислениях.
Замыкания, как и декораторы, позволяют нам создавать на базе функций собственные объекты и систему управления ими.
Декораторы с параметрами
Что если мы хотели бы вызывать обернутую функцию с разными настройками? Для этого нам понадобятся декораторы с параметрами.
Общий синтаксис декоратора с параметрами выглядит так:
К обычному декоратору мы добавляем еще один слой с параметрами. И, конечно, в качестве параметров мы можем передавать другие функции:
И даже функции с собственными параметрами, используя замыкания:
Декоратор filter_by() принимает функцию фильтрации less_than(), затем применяет ее к аргументам обернутой функции, и вызывает обернутую функцию с новыми аргументами.
Выводы
Мы изучили декораторы, мощный механизм для расширения функций. Существует большое количество готовых декораторов, доступных в стандартной библиотеке Python и других библиотеках. Некоторые из них позволяют кэшировать результаты функций, обеспечивать авторизацию и безопасность, профилировать код, проверять типы данных и многое другое.
Декораторы могут повлиять на производительность кода, поэтому их следует использовать с умом и осторожностью. Также нужно учитывать, что декораторы могут усложнить отладку кода и сделать его менее понятным для других разработчиков.
При этом декораторы являются сильным инструментом, который позволяет легко добавлять дополнительную функциональность к существующим функциям и классам и не изменять их исходный код.



