原作者:Pallets / Flask 文档贡献者。本文根据 Flask 3.1.x 官方文档的 Application Factories、Large Applications as Packages 与 Modular Applications with Blueprints 全文合并翻译整理,核验日期为 2026 年 10 月 5 日。
Flask 项目可以从一个文件开始。随着路由、模板、数据库扩展与测试增多,组织代码的重点也会改变:用 Python 包管理模块,用 Blueprint 描述一组功能,再由应用工厂创建实例并完成装配。这三种机制各司其职,也可以配合使用。
本文示例仅做静态审查,没有安装依赖、运行服务器或连接数据库。它们解释组织方式,不能整段拼接成一个完整项目;模块名、模板和扩展配置需要按实际项目补齐。

先把单模块改成包
一个简单应用可能具有这样的结构:
/yourapplication
yourapplication.py
/static
style.css
/templates
layout.html
index.html
login.html
小项目这样完全可以。规模扩大后,可以在外层目录内新建同名的 yourapplication 包,把应用文件、静态资源和模板移进去,再把原来的 yourapplication.py 改名为 __init__.py:
/yourapplication
/yourapplication
__init__.py
/static
style.css
/templates
layout.html
index.html
login.html
原文还提醒清理旧的 .pyc,避免移动后的遗留字节码干扰导入。这是项目迁移提醒,不是让读者删除任意目录的文件;只处理已确认属于该项目、可重新生成的缓存,并先确认源文件已保存。
不要把 python yourapplication/__init__.py 当作这个包的启动方式。应让包能被正常导入,再通过 Flask 命令找到应用。为此,在内层包旁边放置 pyproject.toml:
[project]
name = "yourapplication"
version = "0.1.0"
description = "A Flask application package"
dependencies = [
"Flask>=3.1,<3.2",
]
[build-system]
requires = ["flit_core>=3.2,<4"]
build-backend = "flit_core.buildapi"
编辑修正:原文示例仅提供项目名称与依赖,没有 version 或动态版本声明。本稿补上静态版本和描述,限定本篇的 Flask 3.1 系列,并为 Flit 的 [project] 写法补充版本下限。这些都是编辑增补;未声称原文片段或这里的元数据已经实际安装成功。
在正确项目目录及专用虚拟环境里,原文使用可编辑安装,使包可以被导入,然后运行开发服务器:
pip install -e .
flask --app yourapplication run
以上是供读者审查的命令,没有在本任务中执行。安装会运行所选构建后端并改变环境;只应对可信项目使用。flask run 是开发启动方式,不应作为生产服务部署方案。
理解包结构页的最小全局应用示例
官方包结构页先使用全局应用对象说明导入关系:在 __init__.py 创建应用,再在文件底部导入视图模块,确保路由注册发生在应用存在之后。
# yourapplication/__init__.py
from flask import Flask
app = Flask(__name__)
import yourapplication.views
# yourapplication/views.py
from yourapplication import app
@app.route("/")
def index():
return "Hello World!"
最终目录增加了 pyproject.toml 和 views.py:
/yourapplication
pyproject.toml
/yourapplication
__init__.py
views.py
/static
style.css
/templates
layout.html
index.html
login.html
这里确实存在循环导入:视图依赖包里的 app,包又导入视图。原文解释,在这个特定写法里,底部导入只用于加载视图模块,不是在应用对象尚未建立时使用视图,因此可以工作。但循环导入一般容易出问题;复杂项目更适合下面的工厂与蓝图模式,而不是把这种耦合继续扩散。
应用工厂:把创建实例变成函数
如果在模块导入时直接创建应用,就很难灵活生成不同配置的实例。把创建过程移入函数后,就可以在测试中为不同情况创建不同设置的应用,也可以在同一进程中运行多个配置不同的实例。
工厂最基本的职责是创建应用、加载配置、初始化扩展、注册蓝图,再返回实例:
from flask import Flask
def create_app(config_filename):
app = Flask(__name__)
app.config.from_pyfile(config_filename)
from yourapplication.model import db
db.init_app(app)
from yourapplication.views.admin import admin
from yourapplication.views.frontend import frontend
app.register_blueprint(admin)
app.register_blueprint(frontend)
return app
本稿为原文片段补了 Flask 导入,逻辑保持一致。config_filename 必须来自可信的部署或开发配置,不能直接接受请求中的任意路径:from_pyfile 加载的是 Python 配置文件,不是无执行能力的数据文件。
这种模式意味着蓝图模块导入时还没有具体应用对象。需要访问配置时,可以在请求处理过程中通过 current_app 访问当前实例:
from flask import current_app, Blueprint, render_template
admin = Blueprint("admin", __name__, url_prefix="/admin")
@admin.route("/")
def index():
return render_template(current_app.config["INDEX_TEMPLATE"])
这里从配置读取模板名。current_app 是上下文代理,必须在适当的应用上下文中使用;处理请求时 Flask 会提供相应上下文。不要在模块导入期把它当作随处可用的全局实例。
扩展对象先建立,随后 init_app
工厂模式下,扩展对象最好不要在创建时就绑定某个应用。以 Flask-SQLAlchemy 为例,原文不推荐在工厂里临时创建一个只与当前实例绑定的 db = SQLAlchemy(app);更合适的方式是在模型模块建立尚未绑定的扩展对象:
# yourapplication/model.py
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
然后在工厂中执行 db.init_app(app)。这样,扩展对象不直接保存某个应用专属的状态,一个扩展对象就可以服务多个应用实例。数据库地址等配置仍需在初始化前正确设置。本稿补出的扩展导入仅用于说明依赖;Flask-SQLAlchemy 是单独的包,没有包含在上面的仅 Flask 基础项目依赖中。
找到并调用工厂
对于名为 hello 的独立示例模块,如果工厂叫 create_app 或 make_app,Flask 命令会自动识别:
flask --app hello run
也可以显式给工厂传参:
flask --app 'hello:create_app(local_auth=True)' run
这会调用 hello 中的工厂,并传入关键字参数 local_auth=True。这是另一种工厂签名示例,不能与前面的必填 config_filename 签名直接混用;参数名、默认值和调用方式必须匹配。
工厂还可以继续扩展:允许测试直接传入配置值,免去创建临时配置文件;在装配期间调用蓝图提供的初始化函数,设置请求前后钩子等属性;必要时为应用添加 WSGI 中间件。
Blueprint 记录的是“注册时要做的事”
Blueprint 自 Flask 0.7 引入。它看起来像一个小型 Flask 应用对象,但本身不是应用,而是一组用于构造或扩展应用的操作。调用蓝图路由装饰器时,它先记录注册意图,等蓝图注册到应用上时才真正装配。Flask 在请求分发和 URL 生成时把视图与蓝图关联起来。
它适合把大应用拆成组件,在 URL 前缀或子域下挂载一组路由,为多个应用提供模板过滤器、静态文件或其他工具,也适合扩展在初始化时集中注册功能。蓝图不必拥有视图;同一个蓝图在实现允许的情况下,还可以用不同的注册配置挂载多次。前缀或子域中的参数也可以成为蓝图视图的共同参数与默认值。
需要明确的是,蓝图提供的是 Flask 层面的组织与分离,它们共享应用配置,不构成独立的安全隔离。多个真正的应用对象则拥有独立配置,通常在 WSGI 层进行调度。蓝图一旦注册到已创建的应用上,不能直接卸载;需要重新创建整个应用对象。
一个最小蓝图
from flask import Blueprint, render_template, abort
from jinja2 import TemplateNotFound
simple_page = Blueprint(
"simple_page", __name__, template_folder="templates"
)
@simple_page.route("/", defaults={"page": "index"})
@simple_page.route("/<page>")
def show(page):
try:
return render_template(f"pages/{page}.html")
except TemplateNotFound:
abort(404)
@simple_page.route 记录了以后要注册 show 的意图。蓝图名称只改变端点名:本例端点是 simple_page.show,它不会自动改变 URL。
静态审查提示:本例让路由参数参与选择已有模板,适用于专门存放公开页面的受控目录,不是模板访问权限方案。真实应用若只有有限公开页面,应建立允许列表或明确路由,避免把不应公开的模板变成可访问页面。这不等于把参数当作 Jinja 模板代码执行;本稿没有声称这里已证实存在任意文件读取或模板注入漏洞。
注册到不同的位置
from flask import Flask
from yourapplication.simple_page import simple_page
app = Flask(__name__)
app.register_blueprint(simple_page)
按原文示例,路由表包括应用本身的静态文件端点,以及蓝图的两个页面路由:
/static/<filename> → static
/<page> → simple_page.show
/ → simple_page.show
这些 GET 路由还会具有 HEAD 和自动 OPTIONS 支持。若改为:
app.register_blueprint(simple_page, url_prefix="/pages")
蓝图页面则出现在 /pages/ 和 /pages/<page>,端点名称仍是 simple_page.show。这是两种替代注册方式,不是让同一应用原样执行两次注册。同一蓝图需要重复挂载时,还须满足目标版本的注册命名要求,以及蓝图自身是否支持重复装配。
嵌套蓝图
蓝图可以注册到另一个蓝图上:
parent = Blueprint("parent", __name__, url_prefix="/parent")
child = Blueprint("child", __name__, url_prefix="/child")
parent.register_blueprint(child)
app.register_blueprint(parent)
子蓝图会把父蓝图名称和 URL 前缀都纳入自己。若子蓝图定义了 create 端点,则 url_for('parent.child.create') 对应 /parent/child/create。此处的 create 视图是原文假定存在的端点,以上片段本身没有定义它。
子域也可以组合:
parent = Blueprint("parent", __name__, subdomain="parent")
child = Blueprint("child", __name__, subdomain="child")
parent.register_blueprint(child)
app.register_blueprint(parent)
url_for("parent.child.create", _external=True)
在恰当的主机、子域和端点配置下,原文示意的主机部分是 child.parent.domain.tld;实际外部 URL 还包含协议与路径,并不是上面片段脱离上下文即可输出的固定字符串。父蓝图注册的请求前钩子等也作用于子蓝图;子蓝图没有可处理相应异常的错误处理器时,Flask 会继续尝试父蓝图的处理器。
资源目录、静态文件与模板
资源目录从哪里来
蓝图构造函数的第二个参数通常是 __name__,它指明对应的 Python 模块或包。若对应实际包,包目录就是资源目录;若对应模块,则使用模块所在包的目录。可以查看 simple_page.root_path,原文示例返回 /Users/username/TestProject/yourapplication。多个蓝图可以来自同一目录,但通常不推荐这样组织。
读取资源目录内的文件可以使用:
with simple_page.open_resource("static/style.css") as f:
code = f.read()
这里的路径是固定的项目资源,不应直接替换成未经限制的外部输入。
静态资源的路由并不会自动回退
使用 static_folder 指定静态目录,路径可以是绝对路径,也可以相对于蓝图资源目录:
admin = Blueprint("admin", __name__, static_folder="static")
默认情况下,目录路径的最后一段成为对外路径的一部分,也可以用 static_url_path 修改。若注册前缀为 /admin,默认静态路径就是 /admin/static。端点名称为 admin.static:
url_for("admin.static", filename="style.css")
原文特别提醒:在默认应用静态路由配置下,如果蓝图没有 URL 前缀,它的 /static 会与应用冲突,应用路由优先,因而无法按这种默认配置访问蓝图静态目录。而且,应用静态目录中找不到文件时,不会继续搜索蓝图静态目录。这与模板搜索机制不同。
模板优先级与名称冲突
模板目录通过 template_folder 加入搜索路径:
admin = Blueprint("admin", __name__, template_folder="templates")
应用自己的模板目录优先于蓝图模板目录,因此应用可以覆盖蓝图提供的模板。如果多个蓝图提供相同相对路径的模板,则先注册的蓝图优先。
为了避免意外覆盖,给模板增加功能命名空间。例如,蓝图位于 yourapplication/admin,模板目录为 templates,希望渲染 admin/index.html,那么文件应放在 yourapplication/admin/templates/admin/index.html。多出来的 admin 子目录能避免与应用根级 index.html 混淆。
yourpackage/
blueprints/
admin/
templates/
admin/
index.html
__init__.py
渲染时使用 render_template('admin/index.html')。如果加载结果不符合预期,可在本地诊断时启用 EXPLAIN_TEMPLATE_LOADING,让 Flask 输出每次查找模板的步骤。诊断日志可能包含路径信息,不应不加筛选地公开。
构建 URL 时使用端点名
跨蓝图或从应用其他位置链接到管理首页:
url_for("admin.index")
在同一个蓝图的视图或当前请求渲染的模板里,可以用相对端点:
url_for(".index")
如果当前请求属于 admin 蓝图,后者就会解析到 admin.index。蓝图端点的命名空间与 URL 前缀是两件不同的事。
错误处理:蓝图不能接管所有 404 和 405
蓝图和应用一样支持 errorhandler,可以设置组件自己的错误页面:
@simple_page.errorhandler(404)
def page_not_found(e):
return render_template("pages/404.html"), 404
编辑修正:官方蓝图页此片段只返回模板字符串,没有显式状态码。本稿增加 , 404,避免把错误页面返回为默认的 200 响应。
404 和 405 有一个重要边界:蓝图处理器只能处理在相应蓝图视图内通过异常或 abort 触发的这些错误。访问不存在的 URL 等路由阶段错误,不会自动交给某个蓝图,因为蓝图并不“拥有”整段 URL 空间,应用尚不知道该选择哪个蓝图处理。
如果希望按 URL 前缀为 API 提供不同响应,应在应用层处理,并读取 request:
from flask import request, jsonify
@app.errorhandler(404)
@app.errorhandler(405)
def _handle_api_error(ex):
if request.path.startswith("/api/"):
return jsonify(error=str(ex)), ex.code
else:
return ex
本稿为原文片段补上导入,保留错误的 HTTP 状态。这里处理的是特定 HTTP 错误,不应直接扩展为把所有内部异常详情公开给客户端。
包解决模块组织,蓝图记录组件的注册操作,工厂决定每个应用实例如何组装。把配置加载、扩展初始化与蓝图注册集中到工厂,再明确上下文与资源边界,测试和多实例运行就更容易保持清晰。
来源与许可:三篇原文均为 Flask 官方 3.1.x 文档,原作者为 Pallets 与贡献者。Flask 文档及示例相关版权与 BSD-3-Clause 条款见随附 LICENSE-Flask.txt;编辑增补与修正已逐处标明。
原始版权与许可全文
BSD-3-Clause License Source: https://flask.palletsprojects.com/en/stable/license/ Retrieved 2026-10-05 Copyright 2010 Pallets Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.












暂无评论内容