Этот урок посвящен теме декораторов в Python. Большое внимание уделено свойствам функций в Python, на базе которых реализована идея декораторов. Рассмотрены декораторы принимающие аргументы и возвращающие значение из функции.
- Что нужно знать о функциях в Python, чтобы понимать декораторы?
- Что такое декоратор функции в Python?
- Создание декоратора
- Передача аргументов в функцию через декоратор
- Декораторы для методов класса
- Возврат результата работы функции через декоратор
Что нужно знать о функциях в Python?
Для начала разберем два аспекта связанные с функциями в Python. Во-первых: функция – это объект специального вида, поэтому ее можно передавать в качестве аргумента другим функциям. Во-вторых: внутри функций можно создавать другие функции, вызывать их и возвращать как результат через return. Остановимся на этих моментах более подробно.
Функция как объект
В Python передача одной функции в качестве аргумента другой функции – это нормальная практика. Например, если у вас есть список целых чисел, и вы хотите на базе него получить другой список, элементами которого будут квадраты первого, то такую задачу можно решить в одну строчку.
>>> # исходный список >>> a = [1, 2, 3, 4, 5] >>> # функция, возводящая переданное ей число в квадрат >>> sq = lambda x: x**2 >>> # проверим ее работу >>> print(sq(5)) 25 >>> # получаем список квадратов >>> b = list(map(sq, a)) >>> print(b) [1, 4, 9, 16, 25]
Здесь мы передали функции map в качестве первого аргумента функцию sq, которая будет применяться по очереди ко всем элементам списка a.
В Python функция – это специальный объект, который имеет метод __call__(). Если мы создадим вот такой класс.
class DemoCall(): def __call__(self): return "Hello!"
То объект такого класса можно вызывать как функцию.
>>> hello = DemoCall() >>> hello() 'Hello!'
Функция внутри функции
Вторым важным свойством функции, для понимания темы декораторов, является то, что их можно создавать, вызывать и возвращать из других функций. На этом построена идея замыкания (closures).
Например, создадим функцию, которая умножает два числа.
def mul(a): def helper(b): return a * b return helper
В ней реализованы два важных свойства которые нам понадобятся: внутри функции mul() создается еще одна функция, которая называется helper(); функция mul() возвращает функцию helper как результат работы.
Вызывается эта функция так:
>>>mul(4)(2) 8
Ее главная фишка состоит в том, что можно создавать на базе функции mul() свои кастомизированные функции. Например, создадим функцию “умножение на три”.
>>>three_mul = mul(3) >>>three_mul(5) 15
Как вы можете видеть, мы построили функцию three_mul, которая умножает на три любое переданное ей число.
Что такое декоратор функции в Python?
Конструктивно декоратор в Python представляет собой некоторую функцию, аргументом которой является другая функция. Декоратор предназначен для добавления дополнительного функционала к данной функции без изменения содержимого последней.
Создание декоратора
Предположим у нас есть пара простых функций, вот они:
def first_test(): print("Test function 1") def second_test(): print("Test function 2")
Мы хотим дополнить их так, чтобы перед вызовом основного кода функции печаталась строка “Run function”, а по окончании – “Stop function”.
Сделать это можно двумя способами. Первый – это добавить указанные строки в начало в конец каждой функции, но это не очень удобно, т.к. если мы захотим убрать это, нам придется снова модифицировать тело функции. А если они написаны не нами, либо являются частью общей кодовой базы проекта, сделать это будет уже не так просто. Второй вариант – это воспользоваться знаниями из раздела “Что нужно знать о функциях в Python?”
Создадим вот такую функцию.
def simple_decore(fn): def wrapper(): print("Run function") fn() print("Stop function") return wrapper
Обернем наши функции в эту оболочку.
first_test_wrapped = simple_decore(first_test) second_test_wrapped = simple_decore(second_test)
Функции first_test и second_test остались неизменными.
>>> first_test() Test function 1 >>> second_test() Test function 2
Функции first_test_wrapped и second_test_wrapped обладают функционалом, которого мы добивались.
>>> first_test_wrapped() Run function Test function 1 Stop function >>> first_test_wrapped() Run function Test function 1 Stop function
Если необходимо, чтобы так работали функций с именами first_test и second_test, то можно сделать так.
first_test = first_test_wrapped second_test = second_test_wrapped
Проверим это.
>>> first_test() Run function Test function 1 Stop function >>> second_test() Run function Test function 2 Stop function
То, что мы только что сделали и является реализацией идеи декоратора. Но вместо строк:
def first_test(): print("Test function 1") first_test_wrapped = simple_decore(first_test) first_test = first_test_wrapped
Можно написать вот так:
@simple_decore def first_test(): print("Test function 1")
@simple_decore – это и есть декоратор функции.
Передача аргументов в функцию через декоратор
Если функция в своей работе требует наличие аргумента, то его можно передать через декоратор. Создадим декоратор, который принимает аргумент и выводит информацию о декорируемой функции и ее аргументе.
def param_transfer(fn): def wrapper(arg): print("Run function: " + str(fn.__name__) + "(), with param: " + str(arg)) fn(arg) return wrapper
Для демонстрации ее работы создадим функцию, которая выводит квадратный корень переданного ей числа, в качестве декоратора, укажем только что созданный param_transfer.
@param_transfer def print_sqrt(num): print(num**0.5)
Выполним эту функцию с аргументом 4.
>>> print_sqrt(4) Run function: print_sqrt(), with param: 4 2.0
Декораторы для методов класса
Методы классов также можно объявлять с декоратором. Модифицируем декоратор param_transfer.
def method_decor(fn): def wrapper(self): print("Run method: " + str(fn.__name__)) fn(self) return wrapper
Создадим класс для представления двумерного вектора (из математики). В этом классе определим метод norm(), который выводит модуль вектора.
class Vector(): def __init__(self, px = 0, py = 0): self.px = px self.py = py @method_decor def norm(self): print((self.px**2 + self.py**2)**0.5)
Продемонстрируем работу этого метода.
>>> vc = Vector(px=10, py=5) >>> vc.norm() Run method: norm 11.180339887498949
Возврат результата работы функции через декоратор
Довольно часто, создаваемые функции возвращают какое-либо значение. Для того, чтобы его можно было возвращать через декоратор необходимо соответствующим образом построить внутреннюю функцию.
def decor_with_return(fn): def wrapper(*args, **kwargs): print("Run method: " + str(fn.__name__)) return fn(*args, **kwargs) return wrapper
Этот декоратор можно использовать для оборачивания функций, которые принимают различные аргументы и возвращают значение.
@decor_with_return def calc_sqrt(val): return val**0.5
Выполним функцию calc_sqrt с параметром 16.
>>> tmp = calc_sqrt(16) Run method: calc_sqrt >>> print(tmp) 4.0
P.S.
Если вам интересна тема анализа данных, то мы рекомендуем ознакомиться с библиотекой Pandas. На нашем сайте вы можете найти вводные уроки по этой теме. Все уроки по библиотеке Pandas собраны в книге “Pandas. Работа с данными”.
<<<Python. Урок 18. Аннотация типов в Python Python. Урок 20. Объектная модель в Python>>>
Мое почтение!
Огромное спасибо за вашу работу!
Возможно, я ошибаюсь, но, по-моему, при создании декоратора “simple_decore” в строке 6 допущена синтаксическая ошибка – поставлены скобки при вызове функции “wrapper”.
Да, действительно! Спасибо за обратную связь!
а если я функцию в своем модуле не определяю, а импортирую, как будет выглядеть код с декоратором?
Спасибо большое! Очень доходчиво описано, а с примерами суть усвоилась ещё лучше!