Декораторы в Python

Оглавление

Декораторы - довольно полезная функция Python. Однако может показаться, что любые ресурсы или знания, окружающие их, делают всю концепцию непостижимой. Но на самом деле декораторы довольно просты. Читайте дальше, и мы покажем вам, почему.

Зачем мне нужен декоратор?

Для начала давайте на время проигнорируем Python или программное обеспечение, а вместо этого проиллюстрируем концепцию на примере реального сценария.

Очень рано в жизни мы учимся передвигаться с помощью ходьбы. Позже мы можем научиться передвигаться, катаясь на велосипеде. И водить автомобиль. А возможно, и на скейтборде. Но каким бы способом мы ни учились, мы все равно просто двигаемся, как и всегда.

Чтобы понять концепцию декоратора, представьте, что ходьба, езда на велосипеде, вождение автомобиля и катание на скейтборде - все это поведение, которое дополняет движение: они декорируют поведение перемещения.

Короче говоря, это точная концепция декоратора!

"Велосипед" - это поведение, которое "украшает" способ передвижения чего-либо, в данном случае человека. Ходьба, вождение автомобиля и езда на велосипеде представляют собой альтернативные способы передвижения, которые могут быть применены не только к поведению человека, но и к другим применимым объектам. (Собака, например, может ходить и, возможно, ездить на скейтборде. Однако я не уверен, что он сможет получить водительские права!)

Итак, теперь, когда мы описали концепции, давайте рассмотрим некоторые Python:

>>> def calculate_amount(premium, rate):
...     return rate * premium
...
>>>

Это простая функция, которая вычисляет сумму после применения процентов. И мы используем ее в различных приложениях для расчета влияния процентов. Например, так:

>>> total = calculate_amount(120, 1.10)
>>> total
132.0
>>>

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

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

Подумайте о том, что изменение технологии, например, обновление стороннего компонента, может заставить нас обновить логику регистрации. Мы хотим избежать необходимости затрагивать бизнес-логику: это увеличивает вероятность того, что мы что-то сломаем, что может привести к дополнительному тестированию. Эти дополнительные шаги увеличат время реализации и риск.

Именно здесь декораторы проявляют себя во всей красе! Декоратор приукрашивает нашу бизнес-логику, не изменяя ее, и снижает риски, о которых говорилось выше. Мало того, он позволяет нам выборочно использовать его для регистрации только того, что нам действительно нужно регистрировать - и делать это легко. Таким образом, устраняется ненужное протоколирование, которое может замедлить производительность.

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

Далее рассмотрим процесс проектирования решения, позволяющего вести журнал.

Введение в логирование

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

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

Для данного конкретного примера, возможно, вы уже используете логирование в своем коде Python. Если нет, или если вы используете стандартный модуль протоколирования, позвольте мне представить вам фантастический и простой в использовании новый модуль протоколирования под названием Loguru.

Loguru прост в настройке и использовании, и требует минимального кода настройки для начала протоколирования. Стандартный модуль протоколирования Python является мощным и гибким, но для начинающих может быть сложным в настройке. Loguru дает нам лучшее из обоих миров: вы можете начать с простого и даже иметь возможность вернуться к стандартному модулю протоколирования Python для более сложных сценариев протоколирования. Вы можете взглянуть на вышеупомянутую ссылку, чтобы узнать больше.

Поскольку мы используем наш декоратор для введения протоколирования, давайте посмотрим, как заставить протоколирование работать.

Настройка регистрации в Loguru

Первый:

pip install loguru

Затем запустите новый модуль Python. Первым утверждением будет:

from loguru import logger

Теперь мы можем вернуться к декораторам.

Вспомним, что у нас есть функция calculate_amount, для которой мы хотим регистрировать выполнение, когда она используется в определенных случаях:

def calculate_amount(premium, interest):
    return premium * interest

При использовании декоратора, который мы рассмотрим через минуту, все, что вам нужно сделать, это добавить имя декоратора перед определением функции, например, так:

@log_me
def calculate_amount(premium, interest):
    return premium * interest

В этом случае декоратор называется @log_me

Без декоратора мы видим, что функция возвращает число 132, представляющее сумму с процентами. С декоратором мы получаем то же самое, но больше. Мы увидим больше такого типа поведения, когда посмотрим на функциональность, которую декоратор может предложить за кулисами.

Внедрение декораторов

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

def log_me(func):

Заметьте, что имя функции идентично тому, что появляется после @ в самом декораторе. Также обратите внимание, что я назвал параметр func. Это потому, что log_me принимает функцию в качестве входного параметра.

Теперь давайте реализуем декоратор полностью.

Обратите внимание, что при просмотре кода функция (inner) определена внутри другой функции (log_me). В данном случае мы видим, что Python позволяет определять функции внутри других функций, а иногда и зависит от этого. Мы говорим, что inner является оберткой для func. Это означает, что когда мы украшаем любую функцию (func в коде ниже) символом @log_me, то эта функция оборачивается дополнительной логикой (как показано в inner ниже).

Мы объясним это построчно:

def log_me(func):
   def inner(a,b):
        logger.info(f"{__name__} calculated with {a}, {b}")
        return func(a,b)
   return inner

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

Мы определяем inner как принимающий два параметра, a и b. inner затем выполняет оператор logger из loguru, который регистрирует детали того, что нас просят вычислить.

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

Теперь, когда у нас есть это определение:

@log_me
def calculate_amount(premium, interest):
    return premium * interest

... и запустите этот код:

amount = calculate_amount(120, 1.10)

Мы видим:

2019-02-24 09:51:38.442 | INFO     | __main__:inner:8 - __main__ calculated with 120, 1.1

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

Теперь мы можем добавить декоратор везде, где он нужен, и заниматься логированием бесплатно!

Заключительные замечания и дальнейшие действия

Мы уже видели, как декораторы помогают нам разделить бизнес и технические концепции и применять логику только там, где она необходима. Использование декораторов может показаться более интуитивным, чем определение, но определение очень сложных декораторов тоже может стать привычным с практикой.

Вы заметите, что декораторы широко используются в современном коде Python. Некоторые приложения, которые я использовал лично, включают запоминание значений (то есть повышение производительности за счет того, что функции "запоминают" значения, которые они вычислили в предыдущих вызовах) и в жгутах Pytest для хранения тестовых данных, которые использовали мои собственные тесты. Кроме того, вы можете встретить целые пакеты, построенные на концепции декораторов - особенно веб-фреймворки, такие как Flask. В этих случаях декораторы позволяют вам сосредоточиться на поведении маршрута или конечной точки, не заботясь о том, как фреймворк реализует логику обратного вызова.

Можете ли вы придумать, как с помощью декоратора вывести результаты вычислений в журнал? Другим упражнением может быть поиск того, как добавить декорированные методы в спецификацию класса. Итог: рассмотрите возможность использования декоратора для всего, что нужно прозрачно "обернуть" дополнительной функциональностью.

Вернуться на верх