Как сделать свой декоратор python

Обновлено: 06.07.2024

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

В этой статье мы подробно обсудим декораторы Python.

Как создать декораторов

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

В приведенном выше сценарии мы просто создали декоратор с именем lowercase , который принимает функцию в качестве аргумента. Чтобы опробовать нашу функцию lowercase , нам нужно создать новую функцию, а затем передать ее этому декоратору. Обратите внимание, что поскольку функции являются первоклассными в Python, вы можете назначить функцию переменной или рассматривать ее как таковую. Мы будем использовать этот трюк для вызова функции декоратора:

Обратите внимание, что вы можете объединить вышеприведенные два фрагмента кода в один. Мы создали функцию hello_function () , которая возвращает предложение “HELLO WORLD”. Затем мы вызвали декоратор и передали имя этой функции в качестве аргумента, назначая его переменной “decorate”. При выполнении вы можете увидеть, что полученное предложение было преобразовано в нижний регистр.

Однако есть более простой способ применения декораторов в Python. Мы можем просто добавить символ @ перед именем функции декоратора чуть выше функции, которую нужно украсить. Например:

Как применить несколько декораторов к функции

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

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

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

Наше предложение было разделено на два и преобразовано в нижний регистр, так как мы применили оба lowercase и split_sentence декораторы к hello_function .

Передача аргументов функциям декоратора

Декораторы Python также могут перехватывать аргументы, передаваемые оформленным функциям. Аргументы, в свою очередь, будут переданы оформленной функции во время выполнения. Рассмотрим следующий пример:

В приведенном выше сценарии декоратор принимает два аргумента:, аргумент 1 и аргумент 1 .

Создание Декораторов Общего Назначения

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

Мы можем определить их с помощью аргументов args и **kwargs . Все позиционные и ключевые аргументы хранятся в этих двух переменных соответственно. С помощью args и kwargs мы можем передать любое количество аргументов во время вызова функции. Например:

Как видите, никаких аргументов декоратору передано не было.

Теперь давайте посмотрим, как мы можем передавать значения позиционным аргументам:

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

Два ключевых аргумента были переданы декоратору.

В следующем разделе мы обсудим, как отлаживать декораторы.

Как отлаживать декораторы

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

Например: Если мы попытаемся получить метаданные для декоратора function_with_arguments , мы получим метаданные закрытия оболочки. Давайте продемонстрируем это:

Это представляет большую проблему во время отладки. Однако Python предоставляет functools.wraps декоратор, который может помочь в решении этой задачи. Он работает, копируя потерянные метаданные в ваше оформленное закрытие.

Теперь давайте продемонстрируем, как это работает:

Так как мы использовали functools.wraps в функции-оболочке мы можем проверить метаданные функции на наличие “hello_function”:

Приведенный выше сценарий ясно показывает, что метаданные теперь ссылаются на функцию, а не на оболочку. Я рекомендую вам всегда использовать functools.wraps в любое время, когда вы определяете декоратора. Это значительно облегчит вам отладку.

Вывод

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

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

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

Итак, это были основы работы с функциями. Теперь продвинемся на шаг дальше. В Python разрешено объявлять функции внутри других функций:

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

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

Давайте еще раз пробежимся по коду. Через условный оператор мы возвращаем из функции объекты greet и welcome , а не greet() и welcome() . Почему? Потому что скобки означают вызов функции, без них мы просто передаем сам объект функции. Достаточно ясно? Давайте я чуть подробнее остановлюсь на этом. Когда мы пишем a = hi() , функция hi() исполняется и (поскольку имя по умолчанию yasoob) возвращается функция greet . Если мы изменим код на a = hi(name="ali") , то будет возвращена функция welcome . Мы также можем набрать hi()() , что вернет Вы внутри функции greet() .

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

В прошлом примере мы по сути уже написали декоратор! Давайте изменим его и сделаем немного более полезным:

Для понимания декораторов, сначала надо осознать, что все функции в Python это объекты. Что влечет за собой ряд последствий. Рассмотрим их на простом примере:

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

Ссылки на функции

Еще не запутались? А теперь самая интересная часть. Так как функции это ни что иное как объекты, то следовательно:

  • их можно назначать переменным
  • они могут быть объявлены внутри другой функции

То есть функция может вернуть другую функцию в качестве результата:

Стоп! Это еще не все! Если можно вернуть функцию, то и передать в качестве параметра её тоже можно.

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

Самодельные декораторы

Как сделать их вручную?

Теперь, вы наверное сделаете так, чтобы каждый раз при вызове функции a_stand_alone_function , вместо неё отрабатывала функция a_stand_alone_function_decorated . Все довольно просто, просто верните функцию my_shiny_new_decorator из a_stand_alone_function .

Разоблачение декораторов

В предыдущем примере использован следующий синтаксис декоратора:

Вот так все просто. @decorator - просто ссылка на:

Декораторы это версия паттерна проектирования Декоратор для Python. Вообще в Python включено несколько классических паттернов для облегчения процесса разработки, например итераторы.

Конечно можно накапливать декораторы:

Используем синтаксис декораторов в Python:

Порядок указания декораторов имеет значение:

Наконец ответ на вопрос

В заключении вы видите ответ на сам вопрос:

В принципе на этом можно остановиться, но если желание ещё не пропало узнать новое о декораторах, то читаем дальше.

Передача параметров в функции окруженные декоратором

Применение декораторов к методам

Как можно было уже догадаться, по-сути, методы и функции это одно и тоже в Python, за исключением того, что методы должны получить ссылку на родительский объект в качестве первого параметра ( self ). Это означает, что декораторы для них строятся абсолютно так же. Просто не забывайте про self :

Конечно при создании глобального декоратора, который будет применен для всех функций и методов, нужно использовать *args и **kwargs .

Передача параметров декораторам

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

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

Ничего не изменилось. Происходит вызов my_decorator . Итак, когда вы указывете @my_decorator вы говорите Python вызвать функцию этого декоратора.

Ничего удивительного. Проделаем все то же самое, но пропустим вспомогательные переменные:

Заметили? Мы используем вызов функции с синтаксисом @ . Вернемся к декораторам с аргументами. Если мы применяем функции для генерации декораторов на ходу, то можем и передавать аргументы этой функции, не так ли?

Вот вам и декоратор с аргументами, которые можно задать при помощи переменных.

Как видите, любому декоратору можно передать параметры при таком подходе. Ничего не мешает использовать *args и **kwargs . Но помните, что декораторы вызываются только один раз. Только когда Python импортирует этот скрипт. То есть динамически установить значения этих переменных вы не сможете. Когда вы импортируете скрипт, функция уже использует декоратор, поэтому поменять что-то в последствии не получится.

Еще пример: декоратор декорирующий декоратор

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

Пример его использования:

Да-да, все это выглядит примерно следующим образом: "Чтобы понять рекурсию, сначала надо понять рекурсию". Но в итоге разве нет чувства, что вы освоили что-то новое и интересное?

Правильное применение декораторов

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

Python 2.5 и выше представляет решение последней проблемы. Он включает в себя модуль functools (functools.wraps), который копирует название, модуль и документацию каждой задекориованной функции. Самое интересное - functools.wraps и есть декоратор.

Когда стоит применять декораторы?

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

Сам Python предлагает несколько декораторов: property , staticmethod и т.д. Django использует декораторы для кеширования и отслеживания прав на просмотр. Так что круг применения декораторов практически не ограничен.

Изучаем продвинутые возможности Python, часть 2: замыкания, декораторы, модул. главное изображение

В первой части серии публикаций о продвинутых возможностях Python мы познакомились с итераторами, генераторами и модулем itertools. В сегодняшней публикации речь пойдёт о замыканиях, декораторах и модуле functools.

Декораторы

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

Кстати, если вы не знакомы с замыканиями Python, прежде чем читать дальше ознакомьтесь с дополнением о замыканиях в конце этой статьи. Концепцию декораторов сложно понять, если вы не знакомы с замыканиями.

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

Посмотрите на пример использования. Здесь функции add1 и add2 оформлены с помощью logged , а также дан пример вывода. Заметьте, что формат времени хранится в замыкании возвращаемых функций с декоратором. Поэтому понимание замыканий необходимо для понимания декораторов Python.

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

Следующий пример немного сложнее. Давайте напишем декоратор, который кэширует результат вызова функции в течение указанного в секундах времени. Код ожидает, что переданные в функцию аргументы — хэшируемые объекты (hashable objects), потому что мы используем кортеж с аргументами args в качестве первого параметра и замороженный набор элементов в kwargs в качестве второго параметра, который выступает ключом кэша. У каждой функции будет уникальный кэш dict , который хранится в замыкании функции.

Вот как это используется. Мы применяем декоратор к наивному и неэффективному калькулятору чисел Фибоначчи. Декоратор кэша эффективно применяет к коду паттерн мемоизации. Обратите внимание, что в замыкании fib находятся кэш dict , ссылка на исходную функцию fib , значение аргумента logged , а также значение аргумента timeout . dump_closure описывается в конце статьи после раздела о замыканиях.

Декораторы класса

В предыдущем разделе мы рассмотрели декораторы функций и некоторые необычные способы их применения. Теперь давайте рассмотрим декораторы классов. В данном случае декоратор принимает на вход класс (объект с типом type в Python) и возвращает модифицированный класс.

Первый пример — простая математика. Дано частично упорядоченное множество P. Мы определяем Pd как дуальность P, исключительно если P(x,y)⟺Pd(y,x). Другими словами, речь идёт об обратном порядке. Как можно реализовать это с помощью Python? Предположим, класс определяет порядок с помощью методов __lt__ , __le__ и так далее. Тогда мы можем написать декоратор класса, который заменяет каждую функцию её дуальностью.

Вот как это можно применить к str , чтобы создать новый класс rstr , в котором используется обратный лексикографический порядок.

Теперь можно написать декоратор класса.

Вот как он будет использоваться. Обратите внимание, как здесь обрабатываются переопределённые методы и наследование.

Наш первый пример декораторов класса должен был изменять порядок методов класса. Похожий декоратор, но более полезный, может принимать один из __lt__ , __le__ , __gt__ или __ge__ и __eq__ , и реализовывать остальные для полного упорядочивания класса. Это именно то, что делает декоратор functools.total_ordering . Подробности в документации.

Несколько примеров из Flask

Рассмотрим несколько интересных примеров использования декораторов в Flask.

Более интересный пример — декоратор Flask route , который определяется в классе Flask . Заметьте, что декоратор может быть методом класса. В этом случае в качестве первого параметра используется self . Полный код смотрите в файле app.py. Обратите внимание, декоратор просто регистрирует декорированную функцию как обработчик URL с помощью вызова функции add_url_rule .

Дополнительное чтение

Много информации о декораторах вы найдёте на официальной вики-странице Python. Также можно посмотреть замечательное видео Дэвида Безли о метапрограммировании в Python 3.

Приложение: замыкания

Замыкание — это комбинация функции и множества ссылок на переменные в области видимости функции. Последнее иногда называют ссылочной средой. Замыкание позволяет выполнять функцию за пределами области видимости. В Python ссылочная среда хранится в виде набора ячеек. Доступ к ним можно получить с помощью атрибутов func_closure или __closure__ . В Python 3 используется только __closure__ .

Важно понимать, что речь идёт просто о ссылках, а не о глубоких копиях объектов. Конечно, неважно, являются ли объекты неизменяемыми, но для изменяемых объектов, например, списков, это важно. Это иллюстрирует пример ниже. Обратите внимание, у функций также есть __globals__ , где хранится глобальное ссылочное окружение, для которого была определена функция. Посмотрите на простой пример:

Ещё один пример, более сложный. Убедитесь, что понимаете, почему код работает именно так.

Наконец, вот пример метода dump_closure , который использовался выше.

Читайте также: