Язык программирования Python

Декораторы в Python – простое объяснение с примерами.

Декораторы – это очень полезные инструменты. Поэтому предлагаем пройти шаг за шагом в их освоении. Понять, что они собой представляют и как их использовать.

Создание декоратора

Итак, что же такое декоратор? Прежде чем ответить на этот вопрос, давайте посмотрим, как он выглядит.

@decorator def func(): pass
Code language: CSS (css)

Здесь мы видим, как декоратор с именем decorator применяется к функции func().

Из этого можно сделать вывод, что декораторы – это то, что применяется к функциям.

Фактически, декораторы сами являются функциями, принимая в качестве аргумента одну функцию и возвращая другую.

Более того, приведенный выше код делает то же самое, что и следующий.

def func(): pass func = decorator(func)

Теперь определение имеет немного больше смысла. В этом коде ─ который, повторяю, работает точно так же, как и первый ─ наш декоратор является функцией. Которой мы передаем в качестве аргумента другую функцию (func()), и которая возвращает новую функцию, присвоенную func.

Синтаксис @ позволяет четко видеть, какие декораторы работают над функцией.

Перейдем к конкретному случаю. В качестве примера возьмем следующую функцию.

def add(a, b): return a + b
Code language: JavaScript (javascript)

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

@debug def add(a, b): return a + b
Code language: JavaScript (javascript)

Теперь пришло время написать код для нашего декоратора. Напомним, что это должна быть функция, принимающая в качестве аргумента другую функцию.

def debug(f): pass

Она также должна была вернуть новую функцию. Поэтому сделаем так.

def debug(f): def new_function(): pass return new_function
Code language: JavaScript (javascript)

Напомним, что Python позволяет нам определять функции внутри других функций.

Итак, возвращаясь к нашему декоратору, как мы говорили, код будет соответствовать следующему.

def add(a, b): return a + b add = debug(add)
Code language: JavaScript (javascript)

Поэтому для того, чтобы все вызовы add() в нашей программе продолжали работать, декоратор должен вернуть новую функцию с той же структурой аргументов и операцией, что и оригинальная (то есть, принять два объекта в качестве аргументов и вернуть сумму, которая получается в результате).

Однако наша new_function() не имеет аргументов. Давайте это исправим.

def debug(f): def new_function(a, b): pass return new_function
Code language: JavaScript (javascript)

Наконец, нам не нужно дублировать код add() в new_function(), поскольку он уже предоставлен в качестве аргумента. Мы можем просто сделать:

def debug(f): def new_function(a, b): return f(a, b) return new_function
Code language: JavaScript (javascript)

Идеально! Теперь декоратор может быть применен к нашей функции без ошибок.В результате на экране должно появиться сообщение.

def debug(f): def new_function(a, b): print("Функция add() вызвана!") return f(a, b) return new_function @debug def add(a, b): return a + b print(add(7, 5))
Code language: PHP (php)

Поздравляю! Это был наш первый эксперимент с декораторами.

Давайте пойдем немного дальше. Рассмотрим другую функцию, которая возвращает противоположное число n.

def neg(n): return n * -1
Code language: JavaScript (javascript)

Поскольку наш декоратор был создан специально для функции add(), которая требовала два аргумента, он не сможет быть применен в данном случае.

@debug def neg(n): return n * -1 # TypeError! print(neg(5))
Code language: PHP (php)

Ошибка заключается в следующем.

TypeError: new_function() missing 1 required positional argument: 'b'
Code language: JavaScript (javascript)

Поэтому решением будет изменение структуры функции new_function(), чтобы она принимала один аргумент (как neg()). ─ что теперь приведет к отказу декоратора при применении в add()─ или включает более общую нотацию для приема всех видов позиционных аргументов и аргументов по имени.

def debug(f): def new_function(*args, **kwargs): print(f"Функция {f.__name__}() вызвана!") return f(*args, **kwargs) return new_function
Code language: PHP (php)

(Примечание: не путайте “f” в третьей и четвертой строках! Первый указывает на то, что мы хотим отформатировать строку; второй относится к аргументу f).

Здесь мы изменили сообщение, чтобы оно всегда указывало имя соответствующей функции (f.__name__).

Теперь наш декоратор готов к применению к любому типу функций.

Декораторы с аргументами

Интереснее всего то, что декораторы могут иметь аргументы.

Предположим, что, следуя нашему примеру, мы хотим, чтобы отладка включала опцию прерывания выполнения программы и запуска отладчика при вызове функции.

Синтаксис может выглядеть следующим образом.

@debug(breakpoint=True) def func(): pass
Code language: CSS (css)

Как мы можем это реализовать?

Давайте переведем код в его эквивалент без @-синтаксиса.

def func(): pass func = debug(breakpoint=True)(func)
Code language: PHP (php)

Здесь становится понятнее, что теперь аргументом функции debug() не может быть f, а скорее breakpoint. И что она должна вернуть наш предыдущий декоратор, которому будет передана функция func().

Это будет что-то похожее на:

def debug(breakpoint=False): def debug_decorator(f): def new_function(*args, **kwargs): print(f"Функция {f.__name__}() вызвана!") return f(*args, **kwargs) return new_function return debug_decorator
Code language: PHP (php)

Как вы видите, мы создаем новую функцию debug(), которая будет отвечать за прием аргументов, и внутри нее размещаем наш декоратор, теперь с именем debug_decorator.

Далее мы включаем код для запуска отладчика, когда это необходимо.

import pdb def debug(breakpoint=False): def debug_decorator(f): def new_function(*args, **kwargs): print(f"Функция {f.__name__}() вызвана!") if breakpoint: pdb.set_trace() return f(*args, **kwargs) return new_function return debug_decorator
Code language: PHP (php)

Давайте проведем несколько тестов:

@debug(breakpoint=True) def add(a, b): return a + b @debug() # Нужны скобки! def neg(n): return n * -1 print(neg(5)) print(add(7, 5)) # Этот вызов запускает отладчик.
Code language: PHP (php)

Отлично!

Дополнительные моменты

Распространенной проблемой при использовании декораторов является то, что, поскольку они “инкапсулируют” функцию, к которой применяются, мы можем увидеть, что теряем возможность доступа к атрибутам исходной функции: например, к имени и документации.

Рассмотрим следующий код.

def debug(f): def new_function(*args, **kwargs): print(f"Функция {f.__name__}() вызвана!") return f(*args, **kwargs) return new_function @debug def neg(n): "Возвращаем обратное значение n." return n * -1 print(neg.__name__) # new_function help(neg)
Code language: PHP (php)

Мы видим, что атрибуты __name__ и __doc__ (доступ к которым осуществляется с помощью help()) возвращают значения функции new_function().

Чтобы избежать этого, мы можем использовать стандартный декоратор functools.wraps(), который позаботится о копировании всех атрибутов из исходной функции в новую.

from functools import wraps def debug(f): @wraps(f) def new_function(*args, **kwargs): print(f"Функция {f.__name__}() вызвана!") return f(*args, **kwargs) return new_function
Code language: JavaScript (javascript)

Все просто!

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *