Декораторы – это очень полезные инструменты во Flask. Они бывают разной сложности. Некоторые содержат аргументы, другие – нет. Сегодня поговорим о том, как правильно работать с декораторами, которые принимают аргументы. Это может быть не так просто, но если стараться, всегда можно осилить. В процессе передачи аргументов декораторам может возникнуть немало проблем. Самые основные из них мы сегодня разберем более подробно. Давайте приступать.
Аргументы декораторам – нужны ли вообще?
Декораторы позволяют решить много проблем. И в большом количестве ситуаций они позволяют обойтись вообще без аргументов. Самые простые из них именно так и устроены. Тем не менее, в некоторых случаях они все же нужны, если требуется реализовать дополнительные возможности.
Давайте начнем с такого примера – именитого декоратора app.route, который используется во фреймворке Flask. Возможно, вы уже с ним знакомы, потому что он активно применяется в самых разных веб-приложениях.
@app.route('/foo') def foo(): return 'Это тестовый путь!'
Что этот декоратор делает? Его задача – выполнять регистрацию декорированной функции в качестве обработчика для пути. Здесь есть проблема. Flask дает возможность определить большое количество путей, каждый из которых является взаимосвязанным с другим URL. Следовательно, вам необходимо как-то сказать декоратору, для какого именно адреса осуществляется регистрация обработчика.
Если бы нами не был передан URL в качестве аргумента для декоратора, то тогда у нас не было бы возможности заставить его работать так, как нам надо.
Как создать декоратор с аргументами?
К большому сожалению, добавление аргументов в декоратор – не самая простая задача. Давайте попробуем вернуться к стандартной его структуре, которая использовалась до сих пор.
def my_decorator(f): def wrapped(*args, **kwargs): print('до функции') response = f(*args, **kwargs) print('после функции') return response print('декорируем функцию', f) return wrapped @my_decorator def my_function(a, b): print('внутри функции') return a + b
Здесь мы видим, что декоратором никакие аргументы не принимаются в ходе декорирования функции. Тем не менее, его имплементация принимает аргумент f, с помощью которого Python осуществляется передача ссылки на декорированную функцию.
Конечно, можно ожидать, что аргументы декоратора будут передаваться вместе с аргументом f, но передача декорированной функции возможна лишь в качестве единственного аргумента. Но все даже еще более сложно, поскольку внутренняя функция wrapped() включает «улавливающие все» аргументы. Они, по идее, должны передаваться прямо в декорированную функцию и не смешиваться с аргументами, предназначенными для декоратора. Это же становится причиной, почему добавлять аргументы декоратора некуда.
Давайте все простые декораторы назовем стандартными. Мы видели, что стандартным декоратором является функция, принимающая непосредственно декорированную в качестве аргумента и возвращающая другую, которая занимает ее место. С помощью примера, приведенного выше (то бишь, с функцией my_function())? можно выполнить действия, которые может сделать стандартный декоратор, таким образом.
my_function = my_decorator(my_function)
Это необходимо учитывать, так как декораторы с аргументами создаются поверх стандартных.
Декоратор с аргументами – это функция, которая возвращает стандартный декоратор. Конечно, это довольно трудно для понимания изначально. Поэтому давайте все разберем на примере. Для этого осуществим расширение предыдущего декоратора, чтобы им был принят один аргумент.
def my_decorator(arg): def inner_decorator(f): def wrapped(*args, **kwargs): print('до функции') response = f(*args, **kwargs) print('после функции') return response print('декорируем функцию', f, 'с аргументами', arg) return wrapped return inner_decorator
В этом примере функция inner_decorator фактически аналогична my_decorator(). Единственное отличие по сравнению с тем, что было в предыдущем примере, это то, что оператор print также осуществляет вывод аргументов из arg.
Новая же функция my_decorator() принимает arg в качестве аргумента, а потом возвращает стандартный декоратор, определение которого осуществляется, как внутренней функции.
В такой имплементации можно выделить три уровня функций внутри функций. Аргументы декоратора доступны внутреннему посредством замыкания таким же образом, как и возможно получение доступа к функции f внутренней функцией wrapped(). Так, как замыкания касаются всех уровней внутренних функций, если есть необходимость в этом, возможно использование arg из функции wrapped().
А теперь приведем пример того, как этот декоратор может быть использован.
@my_decorator('foo') def my_function(a, b): print('внутри функции') return a + b
Что касается эквивалента данному декоратору, то им является следующее выражение.
my_function = my_decorator(‘foo’)(my_function)
Как осуществляется маршрутизация во Flask?
Для этого мы будем пользоваться декоратором route. Давайте напишем простую версию декоратора, использующегося для маршрутизации во Flask.
route_map = {} def route(url): def inner_decorator(f): route_map[url] = f return f return inner_decorator
Эта имплементация маршрутизатора собирает все пути и функции к ним в словаре route_map, являющегося глобальным. Так, как этот декоратор регистрирует функции, то смысла в том, чтобы оборачивать декорированную функцию, особого нет. Это же служит причиной, почему внутренний декоратор просто обновляет словарь маршрутов, а потом осуществляет возврат декорированной функции f без изменений.
Давайте приведем пример использования такого декоратора.
@route('/') def index(): pass @route('/users') def get_users(): pass
Если запустить пример, который был приведен выше, а потом осуществить вывод содержимого из route_map, то получим такой результат.
{ '/': <function index at 0x7a9bc16a8cb0>, '/users': <function get_users at 0x7a9bc16a8dd0> }
Вообще, декоратор route во Flask довольно непростой в использовании, и эта тема находится за пределами этой статьи. Чтобы повысить реалистичность этого примера, у нас есть возможность добавить аргумент methods, являющийся необязательным, но при этом также записывающий HTTP-методы в словаре путей.
route_map = {} def route(url, methods=['GET']): def inner_decorator(f): if url not in route_map: route_map[url] = {} for method in methods: route_map[url][method] = f return f return inner_decorator
С этой усовершенствованной версией декоратора есть возможность генерировать и более сложные маршруты, такие как следующие.
@route('/') def index(): pass @route('/users', methods=['GET', 'POST']) def get_users(): pass @route('/users', methods=['DELETE']) def delete_users(): pass
Если говорить о словаре route_map, то для описанного выше примера его содержимое будет следующим.
{ '/': { 'GET': <function index at 0x7a9bc16a8680> }, '/users': { 'GET': <function get_users at 0x7a9bc16a84d0>, 'POST': <function get_users at 0x7a9bc16a84d0>, 'DELETE': <function delete_users at 0x7a9bc16a8a70> } }
Проверка прав доступа через декораторы
Очень часто в процессе работы с веб-приложениями необходимо проверять, имеется ли у клиента разрешение на то, чтобы выполнять то действие, которое он пытается сделать. В данные проверки входит, в том числе, получение значения, основываясь на заголовке HTTP-запроса (токена) либо из cookie браузера, для идентификации клиента. Затем, когда получается распознать клиента, применяется специальный метод для определения прав доступа, которые являются допустимыми в данном случае.
Так, как проверка разрешений в целом зависит от программы, здесь мы продемонстрируем общий пример, где просто необходимо дать разрешение на то, чтобы на основе значения в HTTP заголовка было дано разрешение на выполнение запроса.
Предположим, у нас есть задача пропускать исключительно те запросы, которые используют конкретный user agent, в то время как другие просто выключаются.
Сам код приложения, которое выполняет эту функцию, будет следующим:
from flask import Flask, request, abort app = Flask(__name__) def only_user_agent(user_agent): def inner_decorator(f): def wrapped(*args, **kwargs): if user_agent not in request.user_agent.string.lower(): abort(404) return f(*args, **kwargs) return wrapped return inner_decorator @app.route('/') @only_user_agent('curl') def index(): return 'Hello Curl!'
После того, как мы запустим этот код и перейдем на страницу http://localhost:5000, нами будет получено исключение 404 Not Found. Тем не менее, если вы воспользуетесь curl из терминала, то нам покажется, что запрос выполнен успешно.
(venv) $ curl http://localhost:5000/
Hello Curl!
Выводы
Мы разобрали основные моменты использования аргументов в декораторах. Конечно, это не все, что можно узнать. Python – очень функциональный язык программирования, и чтобы узнать все его тонкости (даже если речь идет исключительно об одном фреймворке), потребуется немало времени. Тем не менее, базовые аспекты вы уяснили.
Конечно, необходимо еще долго практиковаться до того, как эти знания будут на интуитивном уровне. А пока вы еще не закрепили знания, можете использовать это руководство, чтобы выполнять те задачи, которые перед вами ставятся.
Разработка веб-приложений набирает все большую актуальность. И декораторы значительно упрощают этот процесс.