Решение проектных задач в Flask
Если вас интересует, почему Flask делает определенные вещи именно так, а не иначе, этот раздел для вас. Он должен дать вам представление о некоторых проектных решениях, которые могут показаться произвольными и неожиданными на первый взгляд, особенно по сравнению с другими фреймворками.
Явное представление объекта приложения
Веб-приложение Python, основанное на WSGI, должно иметь один центральный вызываемый объект, реализующий фактическое приложение. В Flask это экземпляр класса Flask. Каждое приложение Flask должно создать экземпляр этого класса самостоятельно и передать ему имя модуля, но почему Flask не может сделать это сам?
Без такого явного объекта приложения следующий код:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return 'Hello World!'
Будет выглядеть так:
from hypothetical_flask import route
@route('/')
def index():
return 'Hello World!'
Существует три основных причины. Самая важная заключается в том, что неявные объекты приложения требуют, чтобы в данный момент существовал только один экземпляр. Существуют способы имитировать несколько приложений с использованием одного объекта приложения, например, поддерживая стек приложений, но это создает некоторые проблемы, которые я подробно описывать здесь не буду. Теперь вопрос в том: когда микрофреймворку нужно больше одного приложения одновременно? Хорошим примером является тестирование на единицу. При тестировании чего-либо очень полезно создать минимальное приложение для проверки конкретного поведения. При удалении объекта приложения все выделенные им ресурсы будут освобождены.
Другое, что становится возможным, когда в вашем коде есть явный объект, это возможность создания подкласса базового класса (Flask) для изменения определенного поведения. Это было бы невозможно без хаков, если бы объект создавался заранее на основе класса, который для вас недоступен.
Но есть еще одна очень важная причина, по которой Flask зависит от явного создания этого класса: имя пакета. Всякий раз, когда вы создаете экземпляр Flask, вы обычно передаете ему __name__ в качестве имени пакета. Flask использует эту информацию для правильной загрузки ресурсов относительно вашего модуля. Благодаря отличной поддержке Python для рефлексии он может получить доступ к пакету, чтобы определить, где хранятся шаблоны и статические файлы (см. open_resource()). Разумеется, есть фреймворки, которым не требуется настройка, и они все равно смогут загружать шаблоны относительно модуля вашего приложения. Но им придется использовать текущую рабочую директорию, что является очень ненадежным способом определения расположения приложения. Текущая рабочая директория глобальна для процесса, и если вы запускаете несколько приложений в одном процессе (что может произойти в веб-сервере без вашего ведома), пути будут неправильными. Хуже того: многие веб-серверы не устанавливают рабочую директорию в директорию вашего приложения, а в корень документов, который не обязательно должен быть той же папкой.
Третья причина — «явное лучше неявного». Этот объект — ваше WSGI-приложение, вам не нужно ничего больше помнить. Если вы хотите применить WSGI-мидлвару, просто оберните её, и все готово (хотя есть лучшие способы сделать это, чтобы не потерять ссылку на объект приложения wsgi_app()).
Кроме того, эта архитектура позволяет использовать функцию-фабрику для создания приложения, что очень полезно при тестировании на единицу и в подобных случаях (Фабрики приложений).
Система маршрутизации
Flask использует систему маршрутизации Werkzeug, которая разработана для автоматической сортировки маршрутов по сложности. Это означает, что вы можете объявлять маршруты в произвольном порядке, и они по-прежнему будут работать как ожидается. Это необходимо, если вы хотите правильно реализовать маршрутизацию на основе декораторов, так как декораторы могут вызываться в неопределенном порядке, когда приложение разделено на несколько модулей.
Другое проектное решение системы маршрутизации Werkzeug заключается в том, что маршруты в Werkzeug пытаются гарантировать уникальность URL-адресов. Werkzeug сделает достаточно много, чтобы автоматически перенаправить на канонический URL, если маршрут неоднозначен.
Один движок шаблонов
Flask выбирает один движок шаблонов: Jinja2. Почему Flask не имеет интерфейса для подключения другого движка шаблонов? Вы, очевидно, можете использовать другой движок шаблонов, но Flask всё равно настроит для вас Jinja2. Хотя это ограничение, что Jinja2 всегда настроен, вероятно, исчезнет, решение использовать один движок шаблонов и именно Jinja2 не изменится.
Движки шаблонов похожи на языки программирования, и каждый из этих движков имеет определенное представление о том, как все работает. На первый взгляд они все работают одинаково: вы говорите движку, что нужно оценить шаблон с набором переменных, и берете возвращаемое значение как строку.
Но вот где сходства заканчиваются. Например, Jinja2 имеет расширенную систему фильтров, определенный способ реализации наследования шаблонов, поддержку многоразовых блоков (макросов), которые можно использовать как внутри шаблонов, так и из кода Python, поддерживает итеративное отображение шаблонов, настраиваемый синтаксис и многое другое. С другой стороны, такой движок, как Genshi, основан на оценке потока XML, наследовании шаблонов, учитывая наличие XPath, и многое другое. Mako же обрабатывает шаблоны аналогично модулям Python.
Когда дело доходит до интеграции движка шаблонов с приложением или фреймворком, есть больше, чем просто отображение шаблонов. Например, Flask использует широкую поддержку автоэскейпинга Jinja2. Также он предоставляет способы доступа к макросам из шаблонов Jinja2.
Разработка слоя абстракции для шаблонов, который не лишил бы шаблоны уникальных возможностей, это наука сама по себе и слишком большая задача для микрофреймворка, такого как Flask.
Кроме того, расширения могут легко полагаться на наличие одного языка шаблонов. Вы можете легко использовать свой собственный язык шаблонов, но расширение всё ещё может зависеть от самого Jinja.
Микрофреймворк с зависимостями
Почему Flask называет себя микрофреймворком, но при этом зависит от двух библиотек (Werkzeug и Jinja2)? Почему бы и нет? Если посмотреть на Ruby-сторону веб-разработки, там есть протокол, очень похожий на WSGI. Только он называется Rack, но, помимо этого, он очень похож на WSGI-версию для Ruby. Но практически все приложения в мире Ruby работают не напрямую с Rack, а поверх библиотеки с тем же именем. Эта библиотека Rack имеет два эквивалента в Python: WebOb (ранее Paste) и Werkzeug. Paste всё ещё существует, но, насколько я понимаю, она постепенно устаревает в пользу WebOb. Разработка WebOb и Werkzeug началась бок о бок с похожими идеями: быть хорошей реализацией WSGI для использования другими приложениями.
Flask — это фреймворк, который использует работу, уже проделанную Werkzeug, для правильного взаимодействия с WSGI (что иногда может быть сложной задачей). Благодаря недавним разработкам в инфраструктуре пакетов Python, пакеты с зависимостями больше не являются проблемой, и существует очень мало причин против библиотек, которые зависят от других.
Локальные переменные потока
Flask использует локальные объекты потока (локальные объекты контекста на самом деле, они также поддерживают контексты greenlet) для запросов, сессий и дополнительного объекта, на котором вы можете разместить свои собственные данные (g). Почему это так и не является плохой идеей?
Да, обычно использование локальных переменных потока не является хорошей идеей. Они создают проблемы для серверов, которые не основаны на концепции потоков, и усложняют поддержку крупных приложений. Однако Flask просто не предназначен для крупных приложений или асинхронных серверов. Flask стремится к быстрому и простому написанию традиционного веб-приложения.
Также см. раздел Развитие больших приложений документации для получения вдохновения для больших приложений, основанных на Flask.
Поддержка async/await и ASGI
Flask поддерживает async корутины для функций представлений, выполняя корутину в отдельном потоке вместо использования цикла событий в главном потоке, как это делал бы асинхронный (ASGI) фреймворк. Это необходимо для обратной совместимости Flask с расширениями и кодом, созданным до того, как async была введена в Python. Эта компромиссная мера приводит к снижению производительности по сравнению с ASGI-фреймворками из-за накладных расходов на потоки.
Из-за тесной связи кода Flask с WSGI неясно, возможно ли сделать класс Flask одновременно поддерживающим ASGI и WSGI. В настоящее время в Werkzeug ведутся работы по взаимодействию с ASGI, что в конечном итоге может позволить поддержку в Flask.
См. Использование async и await для более подробного обсуждения.
Что такое Flask, а что Flask не является
Flask никогда не будет иметь слоя базы данных. Он не будет иметь библиотеки форм или чего-либо подобного. Flask просто связывается с Werkzeug для реализации правильного WSGI-приложения и Jinja2 для обработки шаблонов. Он также связывается с некоторыми стандартными библиотеками, такими как logging. Всё остальное предоставлено для расширений.
Почему это так? Потому что у людей разные предпочтения и требования, и Flask не смог бы удовлетворить их, если бы заставлял включать всё это в ядро. Большинству веб-приложений потребуется движок шаблонов. Однако не каждое приложение нуждается в базе данных SQL.
Идея Flask заключается в создании хорошей основы для всех приложений. Всё остальное — за вами или расширениями.
© 2007–2021 Pallets
Licensed under the BSD 3-clause License.
https://flask.palletsprojects.com/en/2.0.x/design/