Декораторы в Python
В этой статье вы узнаете, что такое декораторы, зачем они нужны и как их использовать в Python.
Декоратор — это функция, которая принимает в качестве аргумента другую (оригинальную) функцию и изменяет ее поведение, не изменяя саму функцию. Возвращает другую функцию — замыкание.
При этом:
- Замыкание обычно принимает любой набор позиционных и именованных аргументов.
- Замыкание вызывает оригинальную функцию с помощью переданных аргументов и возвращает результат в декоратор.
Пример декоратора
Для начала определим функцию net_price:
def net_price(price, tax):
""" подсчитывает стоимость товара исходя из цены и налога
Аргументы:
price: цена товара
tax: налог на добавленную стоимость или налог с продаж
Возвращает
стоимость товара
"""
return price * (1 + tax)
Функция net_price рассчитывает итоговую стоимость товара исходя из цены и налога. Она возвращает число net_price — стоимость товара.
Предположим, что вам нужно получить стоимость в долларах. Тогда результат 100 нужно отформатировать так: $100. Для этого можно использовать декоратор.
По определению, декоратор — это функция, которая принимает функцию в качестве аргумента:
def currency(fn):
pass
И возвращает другую функцию:
def currency(fn):
def wrapper(*args, **kwargs):
fn(*args, **kwargs)
return wrapper
Здесь функция currency() возвращает функцию wrapper(). У wrapper() следующие параметры: *args, **kwargs.
Эти параметры позволяют вызывать произволную функцию fn() с любым набором позиционных и именованных аргументов.
В этом примере функция wrapper(), по сути, напрямую выполняет функцию fn(), но не изменяет ее поведение.
Внутри функции wrapper() вы можете вызвать fn(), получить ее результат и отформатировать результат.
Например, таким образом можно добавить знак валюты.
def currency(fn):
def wrapper(*args, **kwargs):
result = fn(*args, **kwargs)
return f'${result}'
return wrapper
Функция currency() — декоратор.
currency() принимает в качестве аргумента любую другую функцию и возвращает результат этой функции со знаком $ перед ним.
Чтобы использовать декоратор currency(), нужно передать функцию net_price, чтобы получить новую функцию и выполнть ее, как если бы это была исходная функция. То есть сделать вот так:
net_price = currency(net_price)
print(net_price(100, 0.05)) # Вывод: $105.0
Символ @
В предыдущем примере функция currency() — декоратор. С ее помощью вы можете декорировать функцию net_price следующим образом:
net_price = currency(net_price)
Общий синтаксис такой:
функция = декоратор(функция)
В Python есть более короткий способ декорирования — с помощью символа @:
@декоратор
def функция():
тело функции
То есть вместо того чтобы писать так:
net_price = currency(net_price)
функцию net_price можно декорировать с помощью currency при создании следующим образом:
@currency
def net_price(price, tax):
""" подсчитывает стоимость товара исходя из цены и налога
Аргументы:
price: цена товара
tax: налог на добавленную стоимость или налог с продаж
Возвращает
стоимость товара
"""
return price * (1 + tax)
Если все соединить вместе, получится такой код:
# декоратор
def currency(fn):
def wrapper(*args, **kwargs):
result = fn(*args, **kwargs)
return f'${result}'
return wrapper
# декорируемая функция
@currency
def net_price(price, tax):
""" подсчитывает стоимость товара исходя из цены и налога
Аргументы:
price: цена товара
tax: налог на добавленную стоимость или налог с продаж
Возвращает
стоимость товара
"""
return price * (1 + tax)
print(net_price(100, 0.05))
Документация декорируемых функций
Функция-декоратор возвращает новую функцию — функцию-обертку (wrapper).
Если вы воспользуетесь встроенной функци help() для вывода документации новых функций, вы не увидите документации для оригинальной функции. Например:
help(net_price)
Вывод
wrapper(*args, **kwargs)
None
А если вы попытаесь вывести свойство __name__ новой функции, Python вернет имя внутренней функции, которую возвращает декоратор:
print(net_price.__name__)
Вывод
wrapper
Что это значит? Когда вы декорируете функцию, вы теряете сигнатуру и документацию оригинальной функции.
Чтобы исправить это, можно использовать функцию wraps() из стандартного модуля functools. На самом деле wraps — тоже декоратор.
Вот, как использовать декоратор wraps:
from functools import wraps
def currency(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
result = fn(*args, **kwargs)
return f'${result}'
return wrapper
@currency
def net_price(price, tax):
""" подсчитывает стоимость товара исходя из цены и налога
Аргументы:
price: цена товара
tax: налог на добавленную стоимость или налог с продаж
Возвращает
стоимость товара
"""
return price * (1 + tax)
help(net_price)
print(net_price.__name__)
Вывод
net_price(price, tax) подсчитывает стоимость товара исходя из цены и налога
Аргументы:
price: цена товара
tax: налог на добавленную стоимость или налог с продаж
Возвращает
стоимость товара
net_price
Что нужно запомнить
- Декоратор — это функция, которая принимает в качестве аргумента другую функцию и изменяет ее поведение, не изменяя саму функцию
- Для декорирования функция можно использовать символ
@. - Чтобы сохранить документацию и название исходной функции используйте функцию
wraps()из встроенного модуляfunctools.
Закрепите практикой
Задачи с автоматической проверкой — решайте прямо здесь, не уходя из учебника.