Функции и методы в Python: передача функции в функцию. Декоратор | OTUS

Функции и методы в 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 функция является специальным объектом, имеющим метод __call__(). Представьте, что мы создали следующий класс:

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) внутри функции mul() мы создаём ещё одну функцию helper(); 2) функция mul() возвращает нам функцию helper() в качестве результата работы.

Вызов этой функции в Python:

>>>mul(4)(2)
8

Особенность заключается в том, что мы можем создавать на базе функции mul() собственные кастомизированные функции. Давайте создадим функцию в Python, умножающую на 3:

>>>three_mul = mul(3)
>>>three_mul(5)
15

В результате была построена функция three_mul(), умножающая на 3 любое переданное ей число.

Декоратор функции в 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")

В нашем случае @simple_decore – это не что иное, как декоратор функции.

Передаём аргументы в функцию с помощью декоратора

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

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-мерного вектора (математического). В классе определим метод 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()
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

Осталось выполнить функцию calc_sqrt() с параметром 16:

>>> tmp = calc_sqrt(16)
Start method: calc_sqrt
>>> print(tmp)

Не пропустите новые полезные статьи!

Спасибо за подписку!

Мы отправили вам письмо для подтверждения вашего email.
С уважением, OTUS!

Автор
3 комментария
0

Как можно правильно читать код python без отступов?

Либо это ошибка форматирования кода при написании статьи, либо это ошибка верстки на сайте. Исправьте, пожалуйста.

def method_decor(fn): def wrapper(self): print("Start method: " + str(fn.name)) fn(self) return wrapper

и т.д.

0

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

0

Исправили, спасибо

Для комментирования необходимо авторизоваться
Популярное
Сегодня тут пусто