Декораторы с аргументами — Часть 3

Декораторы с аргументами

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

Содержание статьи

Далее представлен список статей из данного курса:

Нужны ли декораторам аргументы?

Есть множество проблем, которые можно решить с помощью декораторов, и некоторые из них были рассмотрены в предыдущих уроках данного курса. Однако в некоторых ситуациях могут пригодиться и аргументы. Рассмотрим в качестве примера знаменитый декоратор app.route из веб-фреймворка Flask:

Назначение данного декоратора в регистрации декорированной функции в качестве обработчика для пути в Flask. Проблема в том, что во Flask вы можете определить множество путей, каждый из которых связан с другим URL, поэтому вам нужно каким-то образом сообщить декоратору, для какого URL вы регистрируете обработчик. Если бы мы не передали URL в качестве аргумента для декоратора, то не было бы возможности заставить этот декоратор маршрута работать так как мы задумали.

Создание декоратора с аргументами

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

Здесь видно, что декоратор my_decorator не принимает никаких аргументов, во время декорирования функции, но имплементация этого декоратора принимает аргумент f, через который Python передает ссылку на декорированную функцию.

Можно ожидать, что аргументы декоратора каким-то образом передаются в функцию вместе с аргументом f, но, к сожалению, Python всегда передает декорированную функцию как единственный аргумент функции декоратора. Ситуация усложняется еще тем, что у внутренней функции wrapped() есть «улавливающие все» аргументы, которые передаются непосредственно в декорированную функцию и никогда не должны смешиваться с аргументами, предназначенными для декоратора. По этой причине аргументы декоратора добавлять некуда.

Назовем рассматриваемые до сих пор декораторы «стандартными декораторами«. Мы видели, что стандартный декоратор — это функция, которая принимает декорированную функцию в качестве аргумента и возвращает другую функцию, которая занимает ее место. Используя приведенный выше пример с функцией my_function(), работу, которую выполняет стандартный декоратор, также можно реализовать в Python следующим образом:

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

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

Функция inner_decorator в этом примере практически идентична функции my_decorator() из предыдущего примера. Единственное отличие состоит в том, что оператор print теперь также выводит аргументы из arg. Новая функция my_decorator() принимает arg в качестве аргумента, а затем возвращает стандартный декоратор, определенный как внутренняя функция.

Теперь в этой имплементации есть три уровня функций внутри функций. Аргументы декоратора доступны внутреннему декоратору через замыкание, точно так же, как внутренняя функция wrapped() может получить доступ к функции f. Поскольку замыкания распространяются на все уровни внутренних функций, при необходимости arg также доступен из функции wrapped().

Далее дан пример использования этого декоратора:

Эквивалентом данному новому декоратору в Python является следующее выражение:

Маршрутизация в Flask — Декоратор route

Напишем простую версию декоратора маршрутизации во Flask:

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

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

Если запустить пример выше и затем вывести содержимое из route_map, то будет получено следующее:

На самом деле декоратор route во Flask довольно сложный, и эта тема выходит за рамки данной статьи. Чтобы сделать этот пример более реалистичным, мы можем добавить необязательный аргумент methods, который также записывает HTTP-методы в словаре путей:

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

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

Проверка прав доступа через декораторы

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

Поскольку фактическая проверка разрешений зависит от приложения, здесь будет показан общий пример, в котором просто дается разрешение на выполнение запроса на основе значения из HTTP заголовка. Скажем, нам нужно пропустить только те запросы, которые используют определенный user agent, в то время как все остальные user agent отклоняются:

При запуске данного кода и переходе в браузере на страницу http://localhost:5000 будет получена ошибка "404 Not Found". Однако, если вы откроете страницу через curl из терминала, то запрос будет удачно выполнен:

Заключение

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