首页
/ Flask Quickstart:从零到生产的完整实践指南(最小应用、路由、模板、请求、会话与部署)

Flask Quickstart:从零到生产的完整实践指南(最小应用、路由、模板、请求、会话与部署)

2026-09-04 12:33:20作者:宗隆裙

本篇技术指南以 Flask 官方文档的 Quickstart 为核心骨架,完整覆盖从「最小可运行应用」到「调试、路由、模板渲染、请求数据处理、响应机制、会话、日志与 WSGI 中间件」的全部入门要素,并结合当前仓库 src/flask/ 下的源码实现逐节验证文档行为。读完本篇,你可以独立完成一个可运行、可调试、可部署的 Flask 应用的编写,并理解每个 API 背后的真实调用链。

Flask 交互式调试器界面:浏览器中显示 Traceback 与可执行 Python 代码的控制台

一、最小应用:四行代码理解 WSGI 应用骨架

一个最小 Flask 应用如下(保存为 hello.py):

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello_world():
    return "<p>Hello, World!</p>"

这四行代码完成了四件事:

  1. 导入 Flask。它的实例就是 WSGI 应用程序本身。Flask 类在 Flask 包的公共 API 入口 中导出,该文件同时导出了 Blueprintrequestsessionurl_forrender_templateabortredirectjsonify 等几乎所有入门文档用到的对象。
  2. 创建实例,第一个参数是模块或包名__name__ 是大多数场景下的便捷写法。Flask 需要它来定位资源查找的基准路径——模板(templates 文件夹)和静态文件(static 文件夹)都是相对于这个路径解析的。
  3. @app.route 装饰器告诉 Flask 哪个 URL 触发哪个函数。从源码看,route 定义在 Scaffold.route,其内部装饰器只做一件事:取出 endpoint(默认为视图函数名)后调用 add_url_rule 完成注册;methods 默认值为 ["GET"]HEADOPTIONS 会被自动补充。
  4. 视图函数返回要展示的 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.pywsgi.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?

  1. 反向解析通常比硬编码更具描述性;
  2. 改 URL 时一处修改即可,不必满模板找硬编码地址;
  3. 自动处理特殊字符的转义;
  4. 生成的路径永远是绝对路径,避免浏览器相对路径的意外行为;
  5. 应用挂载在 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。用 routemethods 参数处理多种方法:

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_pathstatic_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 %}

模板内还可直接使用 configrequestsessiong 对象以及 url_forget_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 &lt;blink&gt;hacker&lt;/blink&gt;!</strong>')
>>> Markup.escape('<blink>hacker</blink>')
Markup('&lt;blink&gt;hacker&lt;/blink&gt;')
>>> Markup('<em>Marked up</em> &raquo; 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 响应。完整转换规则:

  1. 若返回的是正确类型的响应对象,直接原样返回;
  2. 若是字符串,用该数据和默认参数创建响应对象;
  3. 若是返回 strbytes 的迭代器/生成器,作为流式响应处理;
  4. 若是字典或列表,用 flask.jsonify 创建响应对象;
  5. 若是元组,元组成员可提供额外信息,形式必须是 (response, status)(response, headers)(response, status, headers)status 覆盖默认状态码,headers 可以是附加头部值的列表或字典;
  6. 以上都不符合时,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。从视图返回 dictlist 即自动转为 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 是标准库 loggingLogger,用法见官方 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 为前提。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
982
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384