楼层: 首页/ 软件技术/ Python Web 开发/ Flask 3.x 实战
2

Flask 3.x 实战

Flask: The Micro Framework

Flask 像瑞士军刀:核心就"路由 + 请求/响应 + 模板",其他全靠插件。这章从第一个 Hello World 讲到一个能跑的博客系统。

安装与第一个应用

pip install flask # 装 Flask 3.x # app.py —— 这就是一个完整的 Flask 应用 from flask import Flask app = Flask(__name__) @app.route("/") def index(): return "你好,Flask!" if __name__ == "__main__": app.run(debug=True) # 开发模式,改代码自动重启

运行

python app.py # * Running on http://127.0.0.1:5000 # 或者用命令行:flask --app app run --debug

路由:动态路由、url_for、redirect、abort

from flask import Flask, url_for, redirect, abort app = Flask(__name__) # 动态路由:<类型:变量名>,类型有 int/float/string/path @app.route("/user/<int:uid>") def profile(uid): return f"用户 {uid}" @app.route("/go-home") def go_home(): # url_for 用函数名反查 URL,改路由不用改代码 return redirect(url_for("index")) @app.route("/admin") def admin(): abort(403) # 直接抛出 HTTP 错误状态码

请求与响应:request / response

from flask import Flask, request, jsonify app = Flask(__name__) @app.route("/api/hello") def hello(): name = request.args.get("name", "陌生人") # ?name=xxx ua = request.headers.get("User-Agent") return jsonify({"msg": f"你好 {name}", "ua": ua}) @app.route("/api/login", methods=["POST"]) def login(): data = request.get_json() # 解析 JSON 请求体 return jsonify({"ok": True, "user": data["username"]})

请求对象 request 全解

request.xxx拿什么
request.argsURL 查询串 ?a=1&b=2
request.formPOST 表单字段
request.get_json()JSON 请求体
request.files上传的文件
request.cookies客户端 cookie
request.headers请求头字典
request.methodGET/POST
request.path / request.url路径 / 完整 URL

响应对象与 jsonify

from flask import make_response, jsonify # 普通返回字符串就是 200 + text/html @app.route("/text") def text(): return "你好" # jsonify 自动设 Content-Type: application/json @app.route("/api") def api(): return jsonify({"ok": True, "data": [1, 2]}) # make_response 自定义状态码和头 @app.route("/custom") def custom(): resp = make_response("Created", 201) resp.headers["X-Custom"] = "hi" return resp

静态文件

# Flask 默认把 static/ 目录挂在 /static # 模板里引用:{{ url_for('static', filename='style.css') }} <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> # 生产:静态文件交给 Nginx,别让 Flask 处理

Jinja2 模板:render_template 与继承

项目结构:templates/base.html、templates/index.html

<!-- templates/base.html 父模板 --> <!DOCTYPE html> <html><body> <h1>我的网站</h1> {% raw %}{% block content %}{% endblock %}{% endraw %} </body></html> {# templates/index.html 子模板继承父模板 #} {% raw %}{% extends "base.html" %} {% block content %} <h2>{{ title }}</h2> <ul> {% for item in items %} <li>{{ item.name }} - {{ item.price }}元</li> {% endfor %} </ul> {% endblock %}{% endraw %}
from flask import Flask, render_template app = Flask(__name__) @app.route("/shop") def shop(): items = [{"name": "苹果", "price": 5}, {"name": "香蕉", "price": 3}] return render_template("index.html", title="水果摊", items=items)

数据库:Flask-SQLAlchemy 与迁移

pip install flask-sqlalchemy flask-migrate from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate app = Flask(__name__) app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///blog.db" db = SQLAlchemy(app) migrate = Migrate(app, db) # 命令行 flask db init/migrate/upgrade class Post(db.Model): id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(120)) body = db.Column(db.Text) # CRUD 速查 p = Post(title="第一篇", body="你好") db.session.add(p); db.session.commit() Post.query.filter_by(title="第一篇").all() Post.query.get(1) p.title = "改了"; db.session.commit() db.session.delete(p); db.session.commit()

应用工厂模式(大型项目必备)

# 在函数里创建 app,方便测试和多环境配置 from flask import Flask from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy() # 先创建扩展,后 init_app def create_app(config_name="default"): app = Flask(__name__) app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///app.db" db.init_app(app) # 扩展绑定到 app from .blueprints.main import main_bp app.register_blueprint(main_bp) return app # 这样测试时可以 create_app(test_config) 用临时数据库

蓝图 Blueprint:把大应用拆成模块

# blueprints/auth.py from flask import Blueprint, render_template auth = Blueprint("auth", __name__, url_prefix="/auth") @auth.route("/login") def login(): return render_template("login.html") # app.py 注册蓝图 app.register_blueprint(auth)

中间件钩子大全

钩子时机 / 用途
before_request请求进来、进视图前(鉴权、打日志)
after_request视图返回后、发响应前(加头、统一格式)
teardown_request响应发出后(清理,即使出错也跑)
before_first_request第一次请求前(初始化,3.0 已移除)

中间件:before_request / after_request

@app.before_request def log_request(): print(f"收到请求 {request.path}") @app.after_request def add_header(response): response.headers["X-App"] = "flask-blog" return response

Jinja2 常用过滤器(管道符 | 后面跟过滤器)

过滤器作用
{{ x|upper }}转大写
{{ x|truncate(20) }}截断到 20 字符
{{ x|default("无") }}空值给默认
{{ x|safe }}不转义 HTML(确认安全才用)
{{ x|date("Y-m-d") }}格式化日期

表单处理与 CSRF 保护(Flask-WTF)

pip install flask-wtf from flask_wtf import FlaskForm from wtforms import StringField, TextAreaField, SubmitField, validators class PostForm(FlaskForm): title = StringField("标题", [validators.InputRequired(), validators.Length(max=120)]) body = TextAreaField("正文", [validators.InputRequired()]) submit = SubmitField("发布") @app.route("/new", methods=["GET", "POST"]) def new_post(): form = PostForm() if form.validate_on_submit(): # POST 且校验通过 p = Post(title=form.title.data, body=form.body.data) db.session.add(p); db.session.commit() return redirect(url_for("post_detail", pid=p.id)) return render_template("new.html", form=form)
SECRET_KEY 别写死

Flask 的 session、WTF CSRF 都靠 app.secret_key 签名。生产环境从环境变量读:app.secret_key = os.environ["SECRET_KEY"],别提交到 git。泄露了等于别人能伪造你的 session。

会话、登录认证与 JWT

# session:把小数据存在客户端 cookie(签名过) from flask import session @app.route("/login", methods=["POST"]) def login(): session["user_id"] = user.id # 写 session,下次请求能读到 return {"ok": True} # Flask-Login:管理登录状态,@login_required 装饰器保护视图 # pip install flask-login from flask_login import LoginManager, login_required, login_user, current_user login_manager = LoginManager() login_manager.init_app(app) @app.route("/dashboard") @login_required def dashboard(): return f"你好 {current_user.name}"

配置管理与环境变量

# config.py:开发/生产配置分离 class BaseConfig: SECRET_KEY = "dev" SQLALCHEMY_TRACK_MODIFICATIONS = False class DevConfig(BaseConfig): DEBUG = True class ProdConfig(BaseConfig): DEBUG = False SECRET_KEY = os.environ["SECRET_KEY"] SQLALCHEMY_DATABASE_URI = os.environ["DATABASE_URL"] app.config.from_object(ProdConfig)

配置管理与错误处理

# 配置:开发/生产分开,读环境变量 import os app.config["SECRET_KEY"] = os.environ.get("SECRET_KEY", "dev-only") app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get( "DATABASE_URL", "sqlite:///dev.db") # 统一错误处理:errorhandler @app.errorhandler(404) def not_found(e): return render_template("404.html"), 404 @app.errorhandler(500) def server_error(e): return {"error": "服务器开小差了"}, 500

错误处理 errorhandler

# 统一处理 404/500,别让用户看到裸栈追踪 @app.errorhandler(404) def not_found(e): return render_template("404.html"), 404 @app.errorhandler(500) def server_error(e): app.logger.exception("500: %s", e) return jsonify({"error": "服务器开小差了"}), 500 # abort() 主动抛错:abort(403) 直接跳到 errorhandler(403)

日志与错误追踪

# Flask 默认把错误打到 stderr,生产要配 logging import logging logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s", handlers=[logging.StreamHandler()], ) @app.errorhandler(500) def internal_error(e): app.logger.exception("服务器错误") # 打完整堆栈 return "服务器开小差了", 500 # 生产建议:用 Sentry 或 Datadog 收错误,别只看 stdout

部署:Gunicorn + Nginx + Docker

# 生产启动(4 个 worker,绑 8000 端口) gunicorn -w 4 -b 127.0.0.1:8000 app:app # Nginx 反向代理(静态文件给 Nginx,动态转发给 Gunicorn) # server { # location /static/ { alias /path/to/static/; } # location / { proxy_pass http://127.0.0.1:8000; } # } # Dockerfile 示例 FROM python:3.13-slim WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY . . CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:8000", "app:app"]

完整案例:Flask 博客系统(目录结构 + 核心代码)

blog/ ├── app.py # 入口,注册蓝图、初始化扩展 ├── models.py # Post/Comment/User 模型 ├── templates/ │ ├── base.html │ ├── list.html # 文章列表+分页 │ └── detail.html # 文章详情+评论 └── static/ # CSS/JS
# models.py from datetime import datetime from app import db class Post(db.Model): id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(120), nullable=False) body = db.Column(db.Text) created = db.Column(db.DateTime, default=datetime.utcnow) comments = db.relationship("Comment", backref="post", lazy=True) class Comment(db.Model): id = db.Column(db.Integer, primary_key=True) body = db.Column(db.String(300)) post_id = db.Column(db.Integer, db.ForeignKey("post.id"))
# app.py 核心路由:列表+分页+详情+评论 from flask import Flask, render_template, request, redirect, url_for from models import db, Post, Comment app = Flask(__name__) app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///blog.db" db.init_app(app) @app.route("/") def post_list(): page = request.args.get("page", 1, type=int) pagination = Post.query.order_by(Post.created.desc()).paginate(page=page, per_page=5) return render_template("list.html", pagination=pagination) @app.route("/post/<int:pid>") def post_detail(pid): post = Post.query.get_or_404(pid) return render_template("detail.html", post=post) @app.route("/post/<int:pid>/comment", methods=["POST"]) def add_comment(pid): c = Comment(body=request.form["body"], post_id=pid) db.session.add(c); db.session.commit() return redirect(url_for("post_detail", pid=pid)) with app.app_context(): # 第一次跑建表 db.create_all()

templates/detail.html 文章详情 + 评论表单

{% raw %}{% extends "base.html" %} {% block content %} <h1>{{ post.title }}</h1> <p class="meta">{{ post.created.strftime("%Y-%m-%d") }}</p> <div class="body">{{ post.body }}</div> <h3>评论 ({{ post.comments|length }})</h3> {% for c in post.comments %} <p>{{ c.body }}</p> {% endfor %} <form method="post" action="/post/{{ post.id }}/comment"> <textarea name="body" required></textarea> <button>发表评论</button> </form> {% endblock %}{% endraw %}

运行

flask --app app run --debug # * Running on http://127.0.0.1:5000 # 浏览器打开 / 看到文章列表,点进 /post/1 看详情并评论
生产环境别用 app.run()

app.run() 是开发服务器,单线程、慢、不安全。生产用 Gunicorn:gunicorn -w 4 -b 0.0.0.0:8000 app:app,前面套 Nginx 处理静态文件和 HTTPS。debug=True 在生产必须关掉——它会暴露调试器,等于把服务器密码贴在公网上。

本章面试题 · Flask

1.(概念题)Flask 里 app_context 和 request_context 有什么区别?

查看答案

答案:request_context 是每个 HTTP 请求一个,含 request/session;app_context 是应用级,绑定 current_app。在请求外(比如命令行、后台线程)用 db.session 之前,必须 with app.app_context():,否则报 "Working outside of application context"。

2.(概念题)Flask 的 request.args 和 request.form 有什么区别?

查看答案

答案:args 取 URL 查询串(?a=1),form 取 POST 表单(application/x-www-form-urlencoded);JSON 请求体用 request.get_json()。三者对应三种数据来源,别拿错。

3.(思考题)Flask 为什么是"微框架"?和 Django 比,大项目会遇到什么问题?

查看答案

答案:Flask 核心只做路由+请求响应+模板,ORM/表单/认证/后台全靠插件。小项目自由,大项目要自己拼规范、选插件版本、统一目录——拼不好就乱。这就是大项目倾向 Django 的原因。