Python. Урок 19. Декораторы функций в Python

Автор: | 09.10.2018

Этот урок посвящен теме декораторов в 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. Работа с данными”.
Книга: Pandas. Работа с данными

<<<Python. Урок 18. Аннотация типов в Python    Python. Урок 20. Объектная модель в Python>>>

Python. Урок 19. Декораторы функций в Python: 4 комментария

  1. Кирилл

    Мое почтение!
    Огромное спасибо за вашу работу!

    Возможно, я ошибаюсь, но, по-моему, при создании декоратора “simple_decore” в строке 6 допущена синтаксическая ошибка – поставлены скобки при вызове функции “wrapper”.

  2. Тим

    а если я функцию в своем модуле не определяю, а импортирую, как будет выглядеть код с декоратором?

  3. Дмитрий

    Спасибо большое! Очень доходчиво описано, а с примерами суть усвоилась ещё лучше!

Добавить комментарий

Ваш адрес email не будет опубликован.