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