Уроки Питон №15/19. Декораторы функций (@simple_decore) в Питон
Урок 19. Декораторы функций в Python
Этот урок посвящен теме декораторов в 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