前言
Flask 是一个 Python 的 Web 框架,官方文档里把它称作「微框架(micro framework)」。这个「微」容易被误解成「功能弱」,其实它的意思是核心很小:Flask 自身只做路由分发和请求响应这最基本的几件事,其它能力(数据库、表单校验、用户登录)都通过扩展来加。所以「微」是一种设计取向,不是能力缺陷。
初学者最常见的三个误解是:以为app.run()启动的那个服务器可以正式上线;以为模板里{{ 变量 }}会自动防住一切注入;以为 Flask 能把 Python 2 代码直接跑起来。前两个后面会重点澄清,第三个要明确说:Python 2 已于 2020 年 1 月 1 日停止维护,当前 Flask 3.1 要求Python 3.9 及以上,并且 3.1 已经移除了对 Python 3.8 的支持。
本文按「最小应用 → 路由 → 请求对象 → 模板 → 静态文件 → 部署与调试」的顺序走一遍。本文所有行为描述以 Flask 官方文档为准,示例基于 Flask 3.x 的写法。
一、最小应用与三种运行方式
一个能跑的最小 Flask 应用只有五行:
# 适用于 Python 3.9+,需先安装:pip install flask
from flask import Flask
app = Flask(__name__) # 第一个参数是模块或包的名称
@app.route("/")
def hello_world():
return "<p>Hello, World!</p>"Flask(__name__)里的__name__告诉 Flask 去哪里找模板和静态文件。官方文档特别提醒:不要把应用文件命名为flask.py,否则会和 Flask 包本身冲突。
运行方式有几种。第一种是用官方推荐的flask命令:
# 在命令行里执行(--app 指定应用所在模块)
flask --app hello run
# 输出示例:Running on http://127.0.0.1:5000第二种是python -m flask --app hello run。如果文件名叫app.py或wsgi.py,官方提供了「自动发现」的捷径,可以省略--app。第三种是在代码里直接app.run()——这在开发时也常见,但正式部署不会用它。
要提醒的是:这个内置服务器「够测试用,但大概不是你生产环境想用的」(官方文档的原话)。它单线程、没有并发优化、没有安全加固,官方明确指向「部署到生产环境」的专章,要求用 WSGI 服务器来跑。
二、路由、视图函数与请求对象
@app.route("/path")这个装饰器把 URL 和它下面的函数绑定起来,被绑定的函数叫视图函数(view function)。它的返回值就是响应给浏览器的内容,默认内容类型是 HTML。
路径里可以带变量:
# 适用于 Python 3.9+
@app.route("/user/<username>")
def show_user_profile(username):
return f"User {username}"<username>是路径参数,会作为同名关键字参数传进视图函数。变量默认匹配任意不含斜杠的文本,也可以在尖括号里写类型转换器,具体用法以官方文档的「路由」章节为准。
不要手工拼 URL,用url_for()反向生成:
# 适用于 Python 3.9+
from flask import url_for
with app.test_request_context():
print(url_for("hello_world")) # /
print(url_for("show_user_profile", username="John Doe"))官方文档解释了为什么该用url_for而不是硬编码:它会让应用挂载在子路径(比如/myapplication而不是/)时仍然正确生成链接,而手写的/就会错。
处理请求方面:默认情况下,一个路由只响应 GET 请求。要处理别的 HTTP 方法,用methods参数:
# 适用于 Python 3.9+(示意片段:do_the_login / show_the_login_form 需自行实现)
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 还提供了@app.get(...)和@app.post(...)这样的快捷装饰器,把不同方法拆到不同函数里。官方文档说明:如果注册了 GET,Flask 会自动加上对 HEAD 的支持,并且按 HTTP 规范自动实现 OPTIONS——你不需要自己处理这两个方法。
请求里的数据通过全局的request对象读取:查询字符串用request.args,表单数据用request.form,请求方法用request.method。这个request是「上下文局部变量」,每个请求都有自己的一份,不要把它存到全局变量里跨请求复用。
三、模板渲染、自动转义与静态文件
视图函数里直接返回一大段 HTML 字符串会失控,所以 Flask 用模板。约定是:模板放在应用包旁边的templates/目录里,用render_template()渲染:
# 适用于 Python 3.9+
from flask import render_template
@app.route("/hello/<name>")
def hello(name):
return render_template("hello.html", person=name)对应的templates/hello.html是一段 Jinja2 模板,例如包含条件判断:
{% if person %}
<h1>Hello {{ person }}!</h1>
{% else %}
<h1>Hello, World!</h1>
{% endif %}{% %}是语句(控制流),{{ }}是表达式(输出值)。模板里还能直接使用url_for()等函数。
现在说重点:Jinja2 默认开启自动转义。官方文档的原话是「Automatic escaping is enabled」,也就是说,如果person里含有 HTML 标签,它会被转义成文本显示,而不会被当成标签执行。这是防御跨站脚本(XSS)的第一道防线。
但这个防线有前提:它只对.html、.htm、.xml、.xhtml这类模板文件默认生效;而且如果你显式使用了|safe过滤器或Markup对象,就等于主动关掉了转义,那就必须自己保证内容可信。手动转义可以用from markupsafe import escape,注意转义函数现在从markupsafe导入——在 Flask 早期版本里flask.escape和flask.Markup还能用,但已被弃用。
再说静态文件。CSS、JavaScript、图片这类不需要模板渲染的文件放在static/目录里,Flask 自动把它们挂到/static下。生成链接时用特殊的端点名:
# 适用于 Python 3.9+
url_for("static", filename="style.css") # 生成 /static/style.css对应的文件要真的存在于static/style.css。官方补充说明:生产环境里理想情况下应由前置的 Web 服务器直接提供静态文件,开发时 Flask 才自己管。
四、调试模式与部署红线
用--debug开启调试模式后,代码改动会自动重载,出错时浏览器里会显示一个交互式调试器:
flask --app hello run --debug这里有一条必须记住的安全红线:官方文档用加粗警告写明「调试器允许从浏览器执行任意 Python 代码」,它虽然有 PIN 码保护,但仍然是重大安全风险,不要把开发服务器或调试器跑在生产环境。同样地,把服务器对外可见时要格外谨慎——默认只监听本机,正是因为调试模式下远程用户可以执行任意代码;只有在关掉调试器或确信网络可信时,才考虑加--host=0.0.0.0。
常见坑点
- 把开发服务器当生产服务器。
❌ 直接用flask run对外提供服务。 ✅ 官方明确该服务器只适合测试;生产环境要用专门的 WSGI 服务器部署。
- 在生产环境开着
debug=True。
❌ 上线时忘了关调试,调试器可执行任意代码,等于把服务器交出去。 ✅ 生产环境必须关闭调试;官方把这条列为重大安全风险。
- 把应用文件命名成
flask.py。
❌ 文件名和 Flask 包同名,import flask导入到自己,启动失败。 ✅ 换个名字,比如hello.py或app.py。
- 硬编码 URL。
❌ 在模板或代码里写死/user/xxx这种路径。 ✅ 用url_for('show_user_profile', username=...)反向生成,应用换挂载前缀时才不会失效。
- 以为模板一定防住了 XSS。
❌ 用|safe过滤用户输入,还以为 Jinja2 会兜底。 ✅ 自动转义只在.html等模板默认开启;用了|safe或Markup就是主动放弃转义,必须自己保证内容可信。
- 从不存在的路径导入
escape。
❌ 照抄旧教程写from flask import escape,得到弃用警告甚至报错。 ✅ 转义与Markup现在从markupsafe导入。
- 用已被移除的
before_first_request。
❌ 老项目里用@app.before_first_request做初始化,在新版 Flask 上报错。 ✅ 该方法在 Flask 2.3 起弃用、3.0 起移除,初始化应放到应用工厂或应用启动代码里。
- 把
request存成全局变量。
❌ 在视图里把request赋给模块级变量,想在别的请求里复用。 ✅request是每个请求独立的上下文对象,只在当前请求内使用。
总结
| 主题 | 关键点 |
|---|
| 创建应用 | app = Flask(__name__),文件名别叫flask.py |
| 运行 | flask --app 模块名 run;app.py/wsgi.py可省略--app |
| 路由 | @app.route("/x")绑定视图函数;methods=[...]指定方法 |
| URL 生成 | url_for('端点名', ...),不要硬编码路径 |
| 模板 | 放在templates/,用render_template();Jinja2 默认自动转义 |
| 静态文件 | 放在static/,用url_for('static', filename=...) |
| 调试 | --debug便于开发,但绝不能用于生产 |
| 版本 | Flask 3.1 要求 Python 3.9+,Python 2 早已停止维护 |
Flask 的入门曲线很平:会写函数、会返回字符串、会用一个装饰器,就能跑起一个网站。真正的功课在后面——理解「开发服务器不等于生产服务器」、理解「自动转义只覆盖默认模板且可被|safe关掉」、理解「调试器是危险功能」。把这三条安全底线记住,再去学扩展,才是稳妥的进阶顺序。