Это третья часть курса по декораторам в Python. В первых двух уроках было показано, как можно создать полезные декораторы, однако ни один из них не принимали аргументов. В данной статье мы рассмотрим процесс создания декораторов, которые принимают настраиваемые аргументы как обычные функции в Python.
Содержание статьи
- Нужны ли декораторам аргументы?
- Создание декоратора с аргументами
- Пример #1: Маршрутизация в Flask — Декоратор route
- Пример #2: Проверка прав доступа через декораторы
Есть вопросы по Python?
На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!
Паблик VK
Одно из самых больших сообществ по Python в социальной сети ВК. Видео уроки и книги для вас!
Далее представлен список статей из данного курса:
- Часть 1: Регистрация функции;
- Часть 2: Изменение поведения функции;
- Часть 3: Декораторы с аргументами (данная статья);
- Часть 4: Классовые декораторы (скоро!)
Нужны ли декораторам аргументы?
Есть множество проблем, которые можно решить с помощью декораторов, и некоторые из них были рассмотрены в предыдущих уроках данного курса. Однако в некоторых ситуациях могут пригодиться и аргументы. Рассмотрим в качестве примера знаменитый декоратор app.route
из веб-фреймворка Flask:
1 2 3 |
@app.route('/foo') def foo(): return 'Это тестовый путь!' |
Назначение данного декоратора в регистрации декорированной функции в качестве обработчика для пути в Flask. Проблема в том, что во Flask вы можете определить множество путей, каждый из которых связан с другим URL, поэтому вам нужно каким-то образом сообщить декоратору, для какого URL вы регистрируете обработчик. Если бы мы не передали URL в качестве аргумента для декоратора, то не было бы возможности заставить этот декоратор маршрута работать так как мы задумали.
Создание декоратора с аргументами
К сожалению, добавить аргументы в декоратор не так уж и просто. Давайте вернемся к стандартной структуре декоратора, которую мы использовали до сих пор:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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 |
Здесь видно, что декоратор my_decorator
не принимает никаких аргументов, во время декорирования функции, но имплементация этого декоратора принимает аргумент f
, через который Python передает ссылку на декорированную функцию.
Можно ожидать, что аргументы декоратора каким-то образом передаются в функцию вместе с аргументом f
, но, к сожалению, Python всегда передает декорированную функцию как единственный аргумент функции декоратора. Ситуация усложняется еще тем, что у внутренней функции wrapped()
есть «улавливающие все» аргументы, которые передаются непосредственно в декорированную функцию и никогда не должны смешиваться с аргументами, предназначенными для декоратора. По этой причине аргументы декоратора добавлять некуда.
Назовем рассматриваемые до сих пор декораторы «стандартными декораторами«. Мы видели, что стандартный декоратор — это функция, которая принимает декорированную функцию в качестве аргумента и возвращает другую функцию, которая занимает ее место. Используя приведенный выше пример с функцией my_function()
, работу, которую выполняет стандартный декоратор, также можно реализовать в Python следующим образом:
1 |
my_function = my_decorator(my_function) |
Это важно иметь в виду, потому что декораторы с аргументами создаются поверх стандартных декораторов.
Декоратор с аргументами определяется как функция, возвращающая стандартный декоратор. Это довольно сложно понять сразу, поэтому разберем все на примере и расширим предыдущий декоратор, чтобы он принял один аргумент:
1 2 3 4 5 6 7 8 9 10 |
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
в качестве аргумента, а затем возвращает стандартный декоратор, определенный как внутренняя функция.
Теперь в этой имплементации есть три уровня функций внутри функций. Аргументы декоратора доступны внутреннему декоратору через замыкание, точно так же, как внутренняя функция wrapped()
может получить доступ к функции f
. Поскольку замыкания распространяются на все уровни внутренних функций, при необходимости arg
также доступен из функции wrapped()
.
Далее дан пример использования этого декоратора:
1 2 3 4 |
@my_decorator('foo') def my_function(a, b): print('внутри функции') return a + b |
Эквивалентом данному новому декоратору в Python является следующее выражение:
1 |
my_function = my_decorator('foo')(my_function) |
Маршрутизация в Flask — Декоратор route
Напишем простую версию декоратора маршрутизации во Flask:
1 2 3 4 5 6 7 |
route_map = {} def route(url): def inner_decorator(f): route_map[url] = f return f return inner_decorator |
Данная имплементация маршрутизатора собирает все пути и функции к ним в глобальном словаре route_map
. Поскольку это декоратор регистрации функции, нет необходимости оборачивать декорированную функцию. По этой причине внутренний декоратор просто обновляет словарь маршрутов, а затем возвращает декорированную функцию f
без изменений.
Пример использования этого декоратора может быть следующим:
1 2 3 4 5 6 7 |
@route('/') def index(): pass @route('/users') def get_users(): pass |
Если запустить пример выше и затем вывести содержимое из route_map
, то будет получено следующее:
1 2 3 4 |
{ '/': <function index at 0x7a9bc16a8cb0>, '/users': <function get_users at 0x7a9bc16a8dd0> } |
На самом деле декоратор route
во Flask довольно сложный, и эта тема выходит за рамки данной статьи. Чтобы сделать этот пример более реалистичным, мы можем добавить необязательный аргумент methods
, который также записывает HTTP-методы в словаре путей:
1 2 3 4 5 6 7 8 9 10 |
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 |
С данной улучшенной версией декоратора можно создать более продвинутые маршруты вроде следующих:
1 2 3 4 5 6 7 8 9 10 11 |
@route('/') def index(): pass @route('/users', methods=['GET', 'POST']) def get_users(): pass @route('/users', methods=['DELETE']) def delete_users(): pass |
Содержимое из словаря route_map
для вышеуказанного примера будет следующим:
1 2 3 4 5 6 7 8 9 10 |
{ '/': { '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, в то время как все остальные user agent отклоняются:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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 из терминала, то запрос будет удачно выполнен:
1 2 |
(venv) $ curl http://localhost:5000/ Hello Curl! |
Заключение
Может показаться, что мы разобрали все возможные случаи использования декораторов, однако осталось еще несколько аспектов, которые стоит разобрать. В следующей части курса будет показано. как создать классовый декоратор, альтернативную структуру для декоратора, которая помогает организовывать более сложные и крупные декораторы.
Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.
E-mail: vasile.buldumac@ati.utm.md
Образование
Universitatea Tehnică a Moldovei (utm.md)
- 2014 — 2018 Технический Университет Молдовы, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»