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

СodeСhick.io - простой и эффективный способ изучения программирования.

2024 ©