Flask Quickstart:从零到生产的完整实践指南(最小应用、路由、模板、请求、会话与部署)
本篇技术指南以 Flask 官方文档的 Quickstart 为核心骨架,完整覆盖从「最小可运行应用」到「调试、路由、模板渲染、请求数据处理、响应机制、会话、日志与 WSGI 中间件」的全部入门要素,并结合当前仓库 src/flask/ 下的源码实现逐节验证文档行为。读完本篇,你可以独立完成一个可运行、可调试、可部署的 Flask 应用的编写,并理解每个 API 背后的真实调用链。
一、最小应用:四行代码理解 WSGI 应用骨架
一个最小 Flask 应用如下(保存为 hello.py):
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello_world():
return "<p>Hello, World!</p>"
这四行代码完成了四件事:
- 导入
Flask类。它的实例就是 WSGI 应用程序本身。Flask类在 Flask 包的公共 API 入口 中导出,该文件同时导出了Blueprint、request、session、url_for、render_template、abort、redirect、jsonify等几乎所有入门文档用到的对象。 - 创建实例,第一个参数是模块或包名。
__name__是大多数场景下的便捷写法。Flask 需要它来定位资源查找的基准路径——模板(templates文件夹)和静态文件(static文件夹)都是相对于这个路径解析的。 - 用
@app.route装饰器告诉 Flask 哪个 URL 触发哪个函数。从源码看,route定义在 Scaffold.route,其内部装饰器只做一件事:取出endpoint(默认为视图函数名)后调用add_url_rule完成注册;methods默认值为["GET"],HEAD和OPTIONS会被自动补充。 - 视图函数返回要展示的 HTML。默认内容类型是 HTML,字符串中的 HTML 会被浏览器直接渲染。
注意:千万不要把应用文件命名为
flask.py,否则会遮蔽 Flask 库本身,导致导入冲突。
二、启动开发服务器:flask run 与端口、网络地址问题
运行应用使用 flask 命令或 python -m flask,必须通过 --app 选项告诉 Flask 应用在哪:
$ flask --app hello run
* Serving Flask app 'hello'
* Running on http://127.0.0.1:5000 (Press CTRL+C to quit)
应用发现行为(Application Discovery):如果文件恰好命名为 app.py 或 wsgi.py,可以省略 --app 选项。完整发现规则见 CLI 文档。仓库中的 cliapp 测试应用 和 factory 模式示例 展示了这两种约定在真实项目中的形态。
这个内置服务器足够测试使用,但生产部署应使用 部署指南 中的方案(如 Gunicorn、Waitress 等)。
端口被占用:如果 5000 端口已被其他程序占用,服务器启动时会报 OSError: [Errno 98](Linux)或 OSError: [WinError 10013](Windows),处理办法见 服务器文档。
对外可见的服务器:默认情况下服务器只能从本机访问。这是有意为之——调试模式下,网络上的用户可以执行任意 Python 代码。如果已禁用调试器或信任局域网用户,可以加上 --host=0.0.0.0 让操作系统监听所有网络接口:
$ flask run --host=0.0.0.0
三、调试模式:自动重载与浏览器内交互式调试器
flask run 不仅能启动开发服务器,启用调试模式后,代码变更会自动重载服务,请求中发生错误时会在浏览器里展示交互式调试器(见文首截图)。
安全警告:调试器允许从浏览器执行任意 Python 代码。虽然受 PIN 码保护,但仍是重大安全风险,绝不要在生产环境运行开发服务器或调试器。
启用方式:
$ flask --app hello run --debug
* Serving Flask app 'hello'
* Debug mode: on
* Running on http://127.0.0.1:5000 (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: nnn-nnn-nnn
相关延伸阅读:服务器运行文档、CLI 文档、内置调试器与其他调试器、日志与错误处理 和 错误处理。
四、HTML 转义:手动防护注入攻击
返回 HTML(Flask 的默认响应类型)时,任何用户提供的值都必须转义,以防御注入攻击。后续引入的 Jinja 模板会自动转义;在纯 Python 中返回 HTML 时,可以用 markupsafe.escape 手动转义:
from flask import request
from markupsafe import escape
@app.route("/hello")
def hello():
name = request.args.get("name", "Flask")
return f"Hello, {escape(name)}!"
如果用户提交 /hello?name=<script>alert("bad")</script>,转义会使其按纯文本渲染,而不是在用户浏览器中执行脚本。示例代码里为了简洁常省略转义,但你必须时刻清楚不可信数据的使用方式。
五、路由:规则、转换器、尾斜杠与 URL 反向解析
5.1 基础路由
用 @app.route 把函数绑定到 URL:
@app.route('/')
def index():
return 'Index Page'
@app.route('/hello')
def hello():
return 'Hello, World'
一个函数可以挂多条规则;URL 中还可以包含变量段。
5.2 变量规则与类型转换器
用 <variable_name> 标记 URL 的动态段,函数会收到同名关键字参数;可选地用 <converter:variable_name> 指定类型:
from markupsafe import escape
@app.route('/user/<username>')
def show_user_profile(username):
# show the user profile for that user
return f'User {escape(username)}'
@app.route('/post/<int:post_id>')
def show_post(post_id):
# show the post with the given id, the id is an integer
return f'Post {post_id}'
@app.route('/path/<path:subpath>')
def show_subpath(subpath):
# show the subpath after /path/
return f'Subpath {escape(subpath)}'
转换器一览:
| 转换器 | 说明 |
|---|---|
string(默认) |
接受不含斜杠的任意文本 |
int |
接受正整数 |
float |
接受正浮点数 |
path |
与 string 相同,但接受斜杠 |
uuid |
接受 UUID 字符串 |
从源码结构看,转换器挂在 app.url_map.converters 上,SansIOMixin.url_map 的文档 明确支持创建类后注入自定义转换器,例如 app.url_map.converters['list'] = ListConverter。转换器测试 覆盖了自定义转换器的注册与匹配行为。
5.3 唯一 URL 与尾斜杠重定向行为
以下两条规则的区别在于尾斜杠:
@app.route('/projects/')
def projects():
return 'The project page'
@app.route('/about')
def about():
return 'The about page'
projects端点的规范 URL 带尾斜杠,类似文件系统的文件夹:访问/projects(无斜杠)时,Flask 会重定向到规范地址/projects/。about端点的规范 URL 不带尾斜杠,类似文件路径:访问/about/(带斜杠)会 404。
这种设计保证同一资源只有一个规范 URL,避免搜索引擎把同一个页面索引两次。
5.4 URL 反向解析:url_for
用 flask.url_for 构建指向某函数的 URL:第一个参数是函数名(端点名),后续关键字参数对应规则中的变量段;多余的未知变量会作为查询参数追加到 URL 尾部。
为什么用反向解析而不在模板中硬编码 URL?
- 反向解析通常比硬编码更具描述性;
- 改 URL 时一处修改即可,不必满模板找硬编码地址;
- 自动处理特殊字符的转义;
- 生成的路径永远是绝对路径,避免浏览器相对路径的意外行为;
- 应用挂载在 URL 根之外(如
/myapplication)时,url_for会正确带上前缀。
用 app.test_request_context 可以在 Python shell 中模拟「正在处理请求」来试用 url_for(其原理见 应用上下文文档):
from flask import url_for
@app.route('/')
def index():
return 'index'
@app.route('/login')
def login():
return 'login'
@app.route('/user/<username>')
def profile(username):
return f'{username}\'s profile'
with app.test_request_context():
print(url_for('index'))
print(url_for('login'))
print(url_for('login', next='/'))
print(url_for('profile', username='John Doe'))
输出:
/
/login
/login?next=/
/user/John%20Doe
注意最后一条:空格被自动转义为 %20,验证了上面第 3 条。
5.5 HTTP 方法
默认路由只响应 GET。用 route 的 methods 参数处理多种方法:
from flask import request
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
return do_the_login()
else:
return show_the_login_form()
上面的写法把所有方法集中在一个函数里,适合各分支共享公共数据的场景。也可以把不同方法拆到不同函数,Flask 2.0 起为每种常用方法提供快捷装饰器:
@app.get('/login')
def login_get():
return show_the_login_form()
@app.post('/login')
def login_post():
return do_the_login()
源码佐证:Scaffold.get / Scaffold.post 等快捷方法都通过 _method_route 转调 self.route(rule, methods=[method]),并且会直接拒绝 methods 参数(TypeError("Use the 'route' decorator to use the 'methods' argument.")),防止语义混淆。
若路由包含 GET,Flask 会自动支持 HEAD 方法并按 HTTP RFC 处理,OPTIONS 也会被自动实现。
六、静态文件:static 文件夹与 static 端点
动态应用需要 CSS、JavaScript 等静态文件。生产环境理想情况下由 Web 服务器负责;开发期间 Flask 可以直接服务——只需在包内或模块旁创建名为 static 的文件夹,应用内即可通过 /static 访问。
生成静态文件 URL 使用特殊的 'static' 端点名:
url_for('static', filename='style.css')
文件必须存放为 static/style.css。从源码看,这一机制在 Flask.init 中自动完成:只要 has_static_folder 为真(构造函数参数 static_folder 默认值为 "static"),就会注册一条 f"{self.static_url_path}/<path:filename>" 规则、端点名固定为 static、视图调用 send_static_file。因此 static_url_path、static_folder 都可以按需在构造函数中改写。
七、模板渲染:Jinja 配置、目录约定与自动转义
在 Python 中手工拼接 HTML 既痛苦又不安全(转义必须自己做),因此 Flask 自动为你配置好 Jinja 模板引擎。模板可以生成任意文本文件——网页应用里主要是 HTML,但 Markdown、邮件纯文本等都可以生成。
渲染模板用 render_template:传入模板名,模板变量作为关键字参数:
from flask import render_template
@app.route('/hello/')
@app.route('/hello/<name>')
def hello(name=None):
return render_template('hello.html', person=name)
Flask 在 templates 文件夹中查找模板。若是模块,文件夹在模块旁边;若是包,文件夹在包内部:
情况 1:模块
/application.py
/templates
/hello.html
情况 2:包
/application
/__init__.py
/templates
/hello.html
完整 Jinja 语法见 Jinja 官方模板文档。一个示例模板:
<!doctype html>
<title>Hello from Flask</title>
{% if person %}
<h1>Hello {{ person }}!</h1>
{% else %}
<h1>Hello, World!</h1>
{% endif %}
模板内还可直接使用 config、request、session、g 对象以及 url_for、get_flashed_messages 函数。不知道 g 是什么?它是用于存储请求期间你自己需要的信息的对象,参考 flask.g 文档 与 SQLite 模式示例。
自动转义默认开启:若 person 含 HTML,会被自动转义。如果你信任某个变量且确定它是安全 HTML(例如来自把 wiki 标记转为 HTML 的模块),可以用 markupsafe.Markup 类或模板中的 |safe 过滤器标记为安全。Markup 的用法:
>>> from markupsafe import Markup
>>> Markup('<strong>Hello %s!</strong>') % '<blink>hacker</blink>'
Markup('<strong>Hello <blink>hacker</blink>!</strong>')
>>> Markup.escape('<blink>hacker</blink>')
Markup('<blink>hacker</blink>')
>>> Markup('<em>Marked up</em> » HTML').striptags()
'Marked up » HTML'
版本说明:自 0.5 起,自动转义不再对所有模板生效,仅以下扩展名触发:
.html、.htm、.xml、.xhtml;从字符串加载的模板自动转义是关闭的。模板继承(让头部、导航、页脚等元素在每页复用)见 模板继承模式。
八、访问请求数据:request 代理、表单、查询参数、上传与 Cookie
8.1 request 为什么是“全局”的
from flask import request
你可能会问:Flask 同时处理多个请求,request 怎么会是全局的?答案是它是一个代理对象,指向当前工作线程正在处理的那个请求,由 Flask 和 Python 的上下文机制内部管理。详见 应用上下文文档。
当前请求方法在 request.method 中。访问表单数据(POST/PUT 提交的数据)用 request.form,它像字典一样:
@app.route("/login", methods=["GET", "POST"])
def login():
error = None
if request.method == "POST":
if valid_login(request.form["username"], request.form["password"]):
return store_login(request.form["username"])
else:
error = "Invalid username or password"
# Executed if the request method was GET or the credentials were invalid.
return render_template("login.html", error=error)
若 form 中不存在该键,会抛出一个特殊的 KeyError:你可以像普通 KeyError 一样捕获它;若不捕获,Flask 会返回 HTTP 400 Bad Request 错误页。也可以用 MultiDict.get 取默认值避免报错。
访问 URL 中的查询参数(?key=value)用 request.args,缺键行为与 form 相同:
searchword = request.args.get('key', '')
完整属性列表见 Request 类文档,其实现位于 flask.wrappers.Request。
8.2 文件上传
处理上传文件要注意两点:HTML 表单必须设置 enctype="multipart/form-data",否则浏览器根本不会传文件;上传的文件先存于内存或文件系统临时位置,通过 request.files 访问。每个上传文件像标准 Python file 对象,还多了 save() 方法:
from flask import request
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
f = request.files['the_file']
f.save('/var/www/uploads/uploaded_file.txt')
...
f.filename 是客户端上传前的文件名,但该值可以被伪造,绝不能直接信任。若要据此在服务器落盘,必须经过 Werkzeug 提供的 secure_filename:
from werkzeug.utils import secure_filename
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
file = request.files['the_file']
file.save(f"/var/www/uploads/{secure_filename(file.filename)}")
...
更完善的上传实践见 文件上传模式文档。
8.3 读写 Cookie
读 Cookie 用 request.cookies(客户端传来的全部 Cookie 组成的字典);写 Cookie 用响应对象的 set_cookie 方法。若需要会话,不要直接操作 Cookie,而是用下一节的 Flask 会话,它在 Cookie 之上加了一层安全机制。
读:
from flask import request
@app.route('/')
def index():
username = request.cookies.get('username')
# use cookies.get(key) instead of cookies[key] to not get a
# KeyError if the cookie is missing.
写:
from flask import make_response
@app.route('/')
def index():
resp = make_response(render_template(...))
resp.set_cookie('username', 'the username')
return resp
注意:Cookie 设置在响应对象上。视图函数通常只返回字符串,Flask 会替你转成响应对象;需要显式修改时用 make_response 先拿到响应对象再改。如果想在响应对象尚不存在的时机设置 Cookie,可以借助 延迟回调模式。
九、重定向与错误
用 flask.redirect 重定向到其他端点,用 flask.abort 带错误码提前终止请求:
from flask import abort, redirect, url_for
@app.route('/')
def index():
return redirect(url_for('login'))
@app.route('/login')
def login():
abort(401)
this_is_never_executed()
这个例子本身没什么意义(用户会被重定向到一个 401 拒绝访问的页面),但它演示了机制:abort 会立即中断后续代码。
每个错误码默认展示一个黑白错误页。自定义错误页用 @app.errorhandler 装饰器:
from flask import render_template
@app.errorhandler(404)
def page_not_found(error):
return render_template('page_not_found.html'), 404
注意 render_template 后面的 404:它告诉 Flask 该页状态码为 404(未找到);若不指定,默认 200,含义是“一切正常”。更多细节见 错误处理文档。
十、深入响应机制:返回值的六种转换规则与 make_response
视图函数的返回值会被自动转换成响应对象。字符串 → 以它为响应体、状态码 200 OK、MIME 类型 text/html 的响应;字典或列表 → 调用 jsonify 生成 JSON 响应。完整转换规则:
- 若返回的是正确类型的响应对象,直接原样返回;
- 若是字符串,用该数据和默认参数创建响应对象;
- 若是返回
str或bytes的迭代器/生成器,作为流式响应处理; - 若是字典或列表,用
flask.jsonify创建响应对象; - 若是元组,元组成员可提供额外信息,形式必须是
(response, status)、(response, headers)或(response, status, headers)。status覆盖默认状态码,headers可以是附加头部值的列表或字典; - 以上都不符合时,Flask 假设返回值是一个合法的 WSGI 应用,并调用它生成响应。
这条规则在 Flask.make_response 中实现:源码先对元组按长度拆包(3 元组直接解包;2 元组靠第二个元素是否为 Headers/dict/tuple/list 判断是头部还是状态码;其他长度直接 TypeError),随后按类型逐一转换。文档同时记录了版本演进:2.2 起生成器转为流式响应、列表转为 JSON 响应;1.1 起字典转为 JSON 响应。
若想在视图内部拿到响应对象做修改,用 flask.make_response。例如给 404 页加自定义响应头:
from flask import make_response
@app.errorhandler(404)
def not_found(error):
resp = make_response(render_template('error.html'), 404)
resp.headers['X-Something'] = 'A value'
return resp
10.1 JSON API
写 API 时常见响应格式是 JSON。从视图返回 dict 或 list 即自动转为 JSON 响应:
@app.route("/me")
def me_api():
user = get_current_user()
return {
"username": user.username,
"theme": user.theme,
"image": url_for("user_image", filename=user.image),
}
@app.route("/users")
def users_api():
users = get_all_users()
return [user.to_json() for user in users]
这是把数据传给 flask.jsonify 的快捷方式,它会序列化所有受支持的 JSON 数据类型——也就是说 dict/list 里的所有数据必须可 JSON 序列化。对数据库模型这类复杂类型,建议先用序列化库(或社区维护的 Flask API 扩展)转换为合法 JSON 类型。
十一、会话(Sessions):基于签名 Cookie 的跨请求状态
除了 request,还有第二个对象 flask.session,用于在相邻请求之间保存与某用户相关的信息。它建立在 Cookie 之上,并对 Cookie 做密码学签名:用户可以查看 Cookie 内容,但除非知道签名用的密钥,否则无法篡改。会话实现见 flask.sessions。
使用前提:设置密钥。
from flask import session
# Set the secret key to some random bytes. Keep this really secret!
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'
@app.route('/')
def index():
if 'username' in session:
return f'Logged in as {session["username"]}'
return 'You are not logged in'
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
session['username'] = request.form['username']
return redirect(url_for('index'))
return '''
<form method="post">
<p><input type=text name=username>
<p><input type=submit value=Login>
</form>
'''
@app.route('/logout')
def logout():
# remove the username from the session if it's there
session.pop('username', None)
return redirect(url_for('index'))
如何生成好的密钥:密钥应尽可能随机,操作系统有基于密码学随机数生成器产生随机数据的方式。快速生成 Flask.secret_key(或配置项 SECRET_KEY):
$ python -c 'import secrets; print(secrets.token_hex())'
'192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf'
Cookie 大小提示:Flask 会把写入 session 的值序列化进 Cookie。如果发现某些值跨请求不持久、Cookie 明明已启用、又没有任何明确报错,请检查页面响应中 Cookie 的大小是否超过了浏览器支持的尺寸。
默认会话基于客户端。若希望改为服务端管理会话,存在多个支持该需求的 Flask 扩展。
十二、消息闪现(Flashing)、日志与 WSGI 中间件
12.1 消息闪现
好的应用靠反馈取胜。Flask 的 flashing 系统允许在请求末尾记录一条消息,并只在**下一个(且仅下一个)**请求中取出,通常配合布局模板展示。发条消息用 flask.flash,取出消息用 flask.get_flashed_messages(模板内也可用)。完整示例见 Flashing 模式文档。
12.2 日志
处理到“本应正确但实际错误”的数据(客户端篡改、客户端代码故障等)时,有时不能只回 400 Bad Request,代码还得继续跑,但值得留下记录。自 Flask 0.3 起,Flask 为你预配置好了 logger:
app.logger.debug('A value for debugging')
app.logger.warning('A warning occurred (%d apples)', 42)
app.logger.error('An error occurred')
app.logger 是标准库 logging 的 Logger,用法见官方 logging 文档;更完整的配置见 日志文档 与 错误处理文档。
12.3 接入 WSGI 中间件
给 Flask 应用加 WSGI 中间件,包装应用的 wsgi_app 属性即可。例如在 Nginx 后面运行时,应用 Werkzeug 的 ProxyFix 中间件以正确解析 X-Forwarded-* 头(场景说明见 Proxy Fix 部署文档):
from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app)
包装 app.wsgi_app 而非 app 本身,意味着 app 仍然指向你的 Flask 应用而不是中间件,后续可以照常使用和配置 app。
十三、下一步:扩展与部署
扩展:扩展是帮你完成常见任务的包,例如 Flask-SQLAlchemy 提供与 Flask 无缝配合的 SQLAlchemy 支持。更多见 扩展文档。
部署:内置开发服务器不适用于生产。准备把新应用上线时,参考 部署指南,其中覆盖了 Gunicorn、uWSGI、Waitress、Nginx 等方案。
安装:以上所有示例均以按 安装文档 完成项目环境搭建并安装 Flask 为前提。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
