Flask для начинающих: первое веб-приложение на Python
Flask на Python с нуля: установка, минимальное приложение, маршруты, шаблоны Jinja2, формы и request, flask run и отладка, структура проекта.
Содержание · 12 разделов
- Установка Flask
- Минимальное приложение и flask run
- Маршруты: URL, параметры и методы
- Шаблоны Jinja2
- Формы и объект request
- Отладка
- Структура проекта на Flask
- Запуск в продакшне
- Когда выбрать Flask, а когда Django или FastAPI
- Частые ошибки
- Error: Could not locate a Flask application
- jinja2.exceptions.TemplateNotFound
- 405 Method Not Allowed
- RuntimeError: The session is unavailable because no secret key was set
- Порт 5000 занят на macOS
- Частые вопросы
- Как подключить CSS, картинки и JavaScript?
- Почему кириллица в JSON выводится как \u041c\u0430...?
- Как подключить базу данных к Flask?
- Можно ли писать асинхронные view во Flask?
- Итог
Flask — минималистичный веб-фреймворк для Python: в ядре только маршрутизация, обработка запросов и шаблоны Jinja2, а база данных, формы и авторизация подключаются расширениями по мере необходимости. Поэтому Flask хорошо подходит для первого веб-приложения: весь сайт может уместиться в одном файле, и каждая строка в нём понятна.
Быстрый старт: установите Flask, создайте файл app.py и запустите сервер разработки.
python -m pip install flask
flask --app app run --debug
# app.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
return "Привет, Flask!"
После запуска откройте http://127.0.0.1:5000/. Ниже разберём, как устроены маршруты, шаблоны и формы, как отлаживать приложение и раскладывать его по файлам. Примеры проверены на Flask 3.1 и Python 3.12.
Установка Flask
Flask ставят в виртуальное окружение, чтобы зависимости проекта не смешивались с системными. Если Python ещё не установлен, начните со статьи «Как установить Python».
Windows (PowerShell):
mkdir flask-app
cd flask-app
py -m venv .venv
.venv\Scripts\Activate.ps1
python -m pip install flask
macOS и Linux:
mkdir flask-app && cd flask-app
python3 -m venv .venv
source .venv/bin/activate
python -m pip install flask
Вместе с Flask установятся его зависимости: Werkzeug (обработка HTTP и сервер разработки), Jinja2 (шаблоны), Click (командная строка) и другие. Проверить версию можно командой flask --version.
Минимальное приложение и flask run
Разберём код из начала статьи:
app = Flask(__name__)создаёт приложение.__name__нужен Flask, чтобы найти папкиtemplatesиstaticрядом с модулем.@app.route("/")связывает URL/с функциейindex.- Функция-представление (view) возвращает ответ: строку, HTML, словарь или список (их Flask превратит в JSON).
Запуск сервера разработки:
flask --app app run --debug
* Serving Flask app 'app'
* Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
Press CTRL+C to quit
* Restarting with stat
* Debugger is active!
Если файл называется app.py или wsgi.py, параметр --app можно опустить: flask run --debug. Порт и адрес меняются флагами --port 8000 и --host 0.0.0.0 (второй открывает сервер для других устройств в сети).
Старый способ — вызвать app.run(debug=True) внутри if __name__ == "__main__": и запустить файл как обычный скрипт: python app.py. Он работает, но для разработки удобнее flask run: команда сама находит приложение, а режим отладки и порт задаются флагами без правки кода.
Маршруты: URL, параметры и методы
from flask import Flask, url_for
from markupsafe import escape
app = Flask(__name__)
@app.route("/hello/<name>")
def hello(name):
return f"Привет, {escape(name)}!"
@app.route("/books/<int:book_id>")
def book_detail(book_id):
return f"Книга №{book_id}"
@app.get("/api/status")
def status():
return {"status": "ok"} # → JSON
Часть URL в угловых скобках становится аргументом функции. Конвертер перед двоеточием задаёт тип: string (по умолчанию, без слешей), int, float, path (со слешами), uuid. Если в /books/<int:book_id> передать abc, Flask сам вернёт 404.
escape() из пакета MarkupSafe экранирует HTML: без него ссылка /hello/<script>... выполнила бы скрипт в браузере. В шаблонах Jinja2 экранирование включено автоматически, поэтому в обычном коде HTML лучше отдавать через шаблоны.
По умолчанию маршрут принимает только GET-запросы. Другие методы разрешают параметром methods=["GET", "POST"] или декораторами-сокращениями @app.get, @app.post, @app.put, @app.delete.
Ссылки не пишут вручную — их строит url_for по имени функции:
url_for("book_detail", book_id=3) # → '/books/3'
Список всех маршрутов приложения покажет команда flask --app app routes.
Шаблоны Jinja2
HTML выносят в папку templates рядом с app.py и отдают через render_template:
flask-app/
├── app.py
└── templates/
├── base.html
└── index.html
Базовый шаблон с общими частями страницы:
<!-- templates/base.html -->
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<title>{% block title %}Библиотека{% endblock %}</title>
</head>
<body>
{% block content %}{% endblock %}
</body>
</html>
Дочерний шаблон наследует его и заполняет блоки:
<!-- templates/index.html -->
{% extends "base.html" %} {% block content %}
<h1>Книги</h1>
<ul>
{% for id, book in books.items() %}
<li>
<a href="{{ url_for('book_detail', book_id=id) }}">{{ book.title }}</a>
— {{ book.author }}
</li>
{% else %}
<li>Пока пусто</li>
{% endfor %}
</ul>
{% endblock %}
from flask import Flask, render_template
app = Flask(__name__)
BOOKS = {
1: {"title": "Мастер и Маргарита", "author": "Булгаков"},
2: {"title": "Пикник на обочине", "author": "Стругацкие"},
}
@app.route("/")
def index():
return render_template("index.html", books=BOOKS)
Основной синтаксис Jinja2: {{ переменная }} выводит значение, {% for %} и {% if %} управляют логикой, {{ value|default("—") }} применяет фильтр, {% extends %} и {% block %} строят наследование. Внутри шаблонов доступна функция url_for. Если вы знакомы с шаблонами Django, синтаксис почти тот же.
Формы и объект request
Данные запроса лежат в объекте request: request.args — параметры строки запроса (?q=...), request.form — поля HTML-формы, request.get_json() — тело JSON, request.files — загруженные файлы, request.method — метод.
from flask import Flask, abort, flash, redirect, render_template, request, url_for
app = Flask(__name__)
app.config["SECRET_KEY"] = "dev" # в продакшне — из переменной окружения
@app.route("/books/<int:book_id>")
def book_detail(book_id):
book = BOOKS.get(book_id)
if book is None:
abort(404)
return render_template("book.html", book=book)
@app.route("/books/new", methods=["GET", "POST"])
def book_create():
if request.method == "POST":
title = request.form.get("title", "").strip()
author = request.form.get("author", "").strip()
if not title:
flash("Укажите название книги")
return render_template("book_form.html"), 400
new_id = max(BOOKS) + 1
BOOKS[new_id] = {"title": title, "author": author}
return redirect(url_for("book_detail", book_id=new_id))
return render_template("book_form.html")
@app.get("/api/books")
def api_books():
q = request.args.get("q", "").lower()
return [book for book in BOOKS.values() if q in book["title"].lower()]
Шаблон формы:
<!-- templates/book_form.html -->
{% extends "base.html" %} {% block content %} {% for message in get_flashed_messages() %}
<p class="error">{{ message }}</p>
{% endfor %}
<form method="post">
<input name="title" placeholder="Название" />
<input name="author" placeholder="Автор" />
<button type="submit">Сохранить</button>
</form>
{% endblock %}
Что здесь происходит:
- после успешного POST делаем
redirect— так обновление страницы не отправит форму повторно; abort(404)прерывает обработку и возвращает страницу «Не найдено»;flash()сохраняет сообщение в сессии до следующего запроса; сессия подписывается ключом, поэтому безSECRET_KEYFlask выдаст ошибку;- view может вернуть кортеж
(тело, статус), например(..., 400).
Для валидации и CSRF-защиты форм обычно подключают расширение Flask-WTF — во Flask «из коробки» CSRF-защиты нет, в отличие от Django.
Отладка
Флаг --debug включает два механизма:
- автоперезагрузку — сервер перезапускается при сохранении файлов;
- интерактивный отладчик — при исключении в браузере открывается трейсбек, и в каждом кадре стека можно выполнять Python-код. Доступ защищён PIN-кодом, который печатается в консоли.
Важно: никогда не включайте режим отладки на сервере, доступном из интернета. Отладчик позволяет выполнять произвольный код на машине.
Без браузера ошибки видны в консоли — как их читать, разобрано в статье «Traceback в Python». Для проверки маршрутов без сервера удобен тестовый клиент:
with app.test_client() as client:
response = client.get("/books/1")
print(response.status_code) # → 200
Структура проекта на Flask
Один app.py удобен для начала, но с ростом проекта приложение раскладывают по модулям: фабрика create_app() создаёт и настраивает приложение, а маршруты группируются в blueprints.
flask-app/
├── library/
│ ├── __init__.py # create_app()
│ ├── books.py # blueprint с маршрутами книг
│ ├── templates/
│ └── static/
└── .venv/
# library/__init__.py
import os
from flask import Flask
def create_app():
app = Flask(__name__)
app.config["SECRET_KEY"] = os.environ.get("SECRET_KEY", "dev")
from .books import bp as books_bp
app.register_blueprint(books_bp, url_prefix="/books")
return app
# library/books.py
from flask import Blueprint, render_template
bp = Blueprint("books", __name__)
@bp.route("/")
def book_list():
return render_template("books/list.html")
Запуск не меняется: flask --app library run --debug — Flask сам найдёт и вызовет функцию create_app. Имена маршрутов внутри blueprint получают префикс: url_for("books.book_list").
Типовые расширения, которые подключают дальше: Flask-SQLAlchemy (ORM и база данных), Flask-Migrate (миграции), Flask-WTF (формы), Flask-Login (вход пользователей).
Запуск в продакшне
Встроенный сервер flask run — только для разработки. В продакшне приложение запускают через WSGI-сервер:
# Linux/macOS
python -m pip install gunicorn
gunicorn -w 4 --bind 127.0.0.1:8000 "app:app"
# Windows: Gunicorn не работает, используйте Waitress
python -m pip install waitress
waitress-serve --host 127.0.0.1 --port 8000 app:app
Перед WSGI-сервером обычно ставят Nginx — схема такая же, как в статье про деплой Django с Gunicorn и Nginx.
Когда выбрать Flask, а когда Django или FastAPI
- Flask — небольшие сайты, внутренние инструменты, прототипы, учебные проекты; когда хочется самому выбрать ORM, формы и структуру.
- Django — сайты с базой данных, пользователями и админкой: всё это есть «из коробки». Начать можно со статьи «Django для начинающих».
- FastAPI — JSON API с автоматической валидацией и документацией OpenAPI, асинхронный код. См. руководство по FastAPI.
Подробное сравнение — в обзоре веб-фреймворков Python и статье FastAPI vs Django vs Flask.
Частые ошибки
Error: Could not locate a Flask application
Flask не нашёл приложение. Укажите его явно: flask --app имя_файла run, или назовите файл app.py/wsgi.py и запускайте команду из папки с ним.
jinja2.exceptions.TemplateNotFound
Шаблон должен лежать в папке templates рядом с модулем, где создан Flask(__name__) (или blueprint с параметром template_folder). Проверьте имя папки — именно templates, во множественном числе.
405 Method Not Allowed
Форма отправляет POST на маршрут, который принимает только GET. Добавьте methods=["GET", "POST"].
RuntimeError: The session is unavailable because no secret key was set
flash() и session требуют SECRET_KEY. Задайте app.config["SECRET_KEY"], а в продакшне берите значение из переменной окружения.
Порт 5000 занят на macOS
Начиная с macOS Monterey (12) порт 5000 занимает служба «Приёмник AirPlay» (AirPlay Receiver). Запустите Flask на другом порту: flask run --port 8000 — или найдите «Приёмник AirPlay» через поиск в Системных настройках и выключите его.
Частые вопросы
Как подключить CSS, картинки и JavaScript?
Положите файлы в папку static рядом с app.py — Flask раздаёт её по адресу /static/. В шаблоне ссылку строят через url_for:
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}" />
Для файла static/css/style.css получится адрес /static/css/style.css. В продакшне статику обычно отдаёт Nginx.
Почему кириллица в JSON выводится как \u041c\u0430...?
Flask по умолчанию экранирует не-ASCII символы в JSON. Клиент после разбора получит правильный текст, но для читаемого ответа экранирование можно отключить:
app = Flask(__name__)
app.json.ensure_ascii = False
После этого {"title": "Мастер и Маргарита"} уходит в ответ без \u-последовательностей. Атрибут app.json появился во Flask 2.2, а ключ JSON_AS_ASCII из старых примеров удалён во Flask 2.3.
Как подключить базу данных к Flask?
Чаще всего — через расширение Flask-SQLAlchemy (pip install flask-sqlalchemy):
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(DeclarativeBase):
pass
db = SQLAlchemy(model_class=Base)
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///library.db"
db.init_app(app)
class Book(db.Model):
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str]
with app.app_context():
db.create_all()
Внутри view запросы выполняются через db.session, например db.session.scalars(db.select(Book)).all(). Для изменения схемы подключают Flask-Migrate. Основы самой ORM — в статье про SQLAlchemy в Python.
Можно ли писать асинхронные view во Flask?
Можно, начиная с Flask 2.0: установите pip install "flask[async]" и объявите view через async def. Flask при этом остаётся WSGI-фреймворком: каждый запрос по-прежнему занимает рабочий поток, а асинхронность помогает только выполнить несколько await-операций параллельно внутри одного запроса. Если сервис строится вокруг асинхронного кода, удобнее ASGI-фреймворк — например, FastAPI.
Итог
Flask даёт минимальный набор: маршруты, request, шаблоны Jinja2 и сервер разработки с отладчиком. Этого хватает для первого веб-приложения, а остальное добавляется расширениями. Чтобы выбрать инструмент для следующего проекта, загляните в обзор фреймворков Python для веб-разработки и раздел «Веб-разработка на Python».
- #Flask
- #Python
- #Jinja2
- #Веб-разработка
- #Для начинающих