Декораторы в 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
.