首页
/ Flask Blueprint 深度指南:用蓝图构建可复用的模块化 Web 应用

Flask Blueprint 深度指南:用蓝图构建可复用的模块化 Web 应用

2026-09-03 15:41:31作者:翟江哲Frasier

Blueprint(蓝图)是 Flask 中组织大型应用的核心机制:它允许把路由、模板、静态文件和回调函数封装为可延迟注册的组件,再挂载到任意应用上,实现共享配置的应用内模块化。本篇基于 Flask 官方文档 docs/blueprints.rst 与仓库源码(Blueprint 实现注册入口)系统讲解蓝图的适用场景、创建与注册、嵌套挂载、资源目录、URL 构建与错误处理,并深入 BlueprintSetupStaterecord/record_once 的延迟执行机制,帮你写出可验证、可复用的蓝图代码。

一、为什么需要 Blueprint

Flask 用蓝图的目的是支撑应用内(或跨应用)的常见组织模式。根据官方文档,蓝图主要面向以下场景:

  • 把应用拆分为多个蓝图:大型项目的理想结构是实例化一个应用对象、初始化若干扩展、再注册一批蓝图;
  • 在 URL 前缀和/或子域上注册蓝图:URL 前缀/子域中的参数会成为该蓝图所有视图函数的公共视图参数(带默认值);
  • 把同一个蓝图注册多次,使用不同的 URL 规则(是否能多次注册取决于蓝图自身的实现方式);
  • 通过蓝图提供模板过滤器、静态文件、模板等工具——蓝图不一定要包含视图函数;
  • 扩展初始化时注册蓝图:Flask 扩展普遍依赖蓝图作为向应用注册操作的中央机制。

需要明确一个边界:蓝图不是可插拔的子应用。它不是应用,而是"一组可以注册到应用上的操作",甚至可以注册多次。从源码结构看,Blueprint 类维护一个 deferred_functions 列表(见 blueprints.py#L204),装饰器只是把操作"记下来",并不立即生效。如果你想拥有真正相互隔离的应用(各自独立的配置),应在 WSGI 层组合多个 Flask 对象,参见 appdispatch 模式文档

蓝图在 Flask 层面提供隔离,共享应用配置,且注册时可以按需修改应用对象。代价是:应用创建后无法反注册某个蓝图,除非销毁整个应用对象。

二、Blueprint 的核心概念:延迟操作

蓝图的基本概念是:记录一系列操作,等被注册到应用时再执行。Flask 在分派请求和在端点之间生成 URL 时,都会把视图函数与其所属蓝图关联起来。

源码层面这一机制由两类对象承载(见 src/flask/sansio/blueprints.py):

  1. Blueprint.record(func):把回调加入 deferred_functions,注册时对每次注册都会执行;
  2. Blueprint.record_once(func):包装一层 wrapper,仅当 state.first_registration 为真时才调用,即"同一应用上只执行一次"。过滤器、测试、全局变量等 Jinja 资源正是用 record_once 注册的,避免重复注册导致重复添加;
# src/flask/sansio/blueprints.py 中的 record_once 核心逻辑
def wrapper(state: BlueprintSetupState) -> None:
    if state.first_registration:
        func(state)
self.record(update_wrapper(wrapper, func))
  1. BlueprintSetupState:注册时由 make_setup_state() 创建的临时对象,携带 appblueprintoptionsfirst_registration 以及合并后的 subdomainurl_prefixurl_defaults@blueprint.route 最终就是调用 state.add_url_rule(...),其中端点被自动拼成 f"{name_prefix}.{name}.{endpoint}"(见 blueprints.py#L110-L116)。

另外有一个重要的护栏:蓝图一旦完成过至少一次注册,再调用 recordadd_url_rule 等 setup 方法会触发 _check_setup_finished,抛出 AssertionError,提示"所有导入、装饰器必须在注册之前完成"(见 blueprints.py#L213-L221)。

三、我的第一个蓝图

一个最简单的蓝图:渲染静态模板页面(官方文档示例,可直接复制使用):

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蓝图名不改变 URL,只改变端点名

Blueprint 构造函数参数说明(依据 blueprints.py#L174-L211 及类文档):

参数 说明
name 蓝图名,会加在每个端点名前。不能为空,也不能包含 .(构造时会抛出 ValueError
import_name 蓝图包名,通常是 __name__,用于定位 root_path
static_folder 静态文件目录,相对于蓝图 root_path;默认关闭
static_url_path 静态文件服务路径,默认为 static_folder
template_folder 加入应用模板搜索路径的目录;优先级低于应用模板目录
url_prefix 加到蓝图所有 URL 前的路径
subdomain 蓝图路由默认匹配的子域
url_defaults 蓝图路由视图参数的默认值字典
root_path 默认为空时由 import_name 自动推断;推断失败时可手动指定
cli_group 控制蓝图 CLI 命令组名,默认值时命令挂在蓝图名下

四、注册蓝图:register_blueprint 与 URL 前缀

注册方式:

from flask import Flask
from yourapplication.simple_page import simple_page

app = Flask(__name__)
app.register_blueprint(simple_page)

此时检查 app.url_map,会得到:

>>> app.url_map
Map([<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
 <Rule '/<page>' (HEAD, OPTIONS, GET) -> simple_page.show>,
 <Rule '/' (HEAD, OPTIONS, GET) -> simple_page.show>])

第一条来自应用自身的静态文件路由,后两条来自 simple_page.show,端点均以 simple_page. 为前缀、以点(.)分隔。

app.register_blueprint 本身很薄(见 sansio/app.py#L570-L595),核心一行是 blueprint.register(self, options):把关键字参数原样转发给蓝图的 register 方法,其中 url_prefixsubdomainurl_defaults覆盖蓝图构造时设置的默认值,额外的关键字参数可放进 Blueprint.record 回调读取的 state.options

挂载到不同位置

app.register_blueprint(simple_page, url_prefix='/pages')

生成的规则变为:

>>> app.url_map
Map([<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
 <Rule '/pages/<page>' (HEAD, OPTIONS, GET) -> simple_page.show>,
 <Rule '/pages/' (HEAD, OPTIONS, GET) -> simple_page.show>])

前缀拼接由 BlueprintSetupState.add_url_rule 完成:"/".join((url_prefix.rstrip("/"), rule.lstrip("/")))(见 blueprints.py#L98-L102)。这套拼接对各种斜杠组合是健壮的——测试 test_blueprint_prefix_slash 用参数化用例验证了 ("", "/")("/foo/", "//bar") 等 10 种组合都得到正确的最终 URL。

同一蓝图注册多次

app.register_blueprint(bp, url_prefix="/1", url_defaults={"bar": 23})
app.register_blueprint(bp, name="test2", url_prefix="/2", url_defaults={"bar": 19})

测试 test_blueprint_url_defaults 验证:两次注册产生 /1/foo/2/foo,各自的 url_defaults 互不影响。两个关键规则:

  • 同一蓝图用同名注册多次会抛 ValueError(2.1 起的行为),必须用 name= 提供唯一名字,见 Blueprint.register 中的重名检查
  • 是否"能"多次注册取决于蓝图实现,扩展类蓝图通常依赖 record_once 保证资源只添加一次。

五、嵌套 Blueprint

蓝图可以注册到另一个蓝图上:

parent = Blueprint('parent', __name__, url_prefix='/parent')
child = Blueprint('child', __name__, url_prefix='/child')
parent.register_blueprint(child)
app.register_blueprint(parent)

子蓝图的名字会加上父蓝图名前缀,子蓝图的 URL 也会带上父蓝图的 URL 前缀,因此 url_for('parent.child.create') 生成 /parent/child/create

子域(subdomain)的合并规则:子蓝图获得父蓝图的子域作为基础,若自身也有子域则以其为前缀,url_for('parent.child.create', _external=True)parent/child 子域嵌套下得到 "child.parent.domain.tld"。这一拼接逻辑在 Blueprint.register 中对每个子蓝图逐项合并 subdomainurl_prefixname_prefix(见 blueprints.py#L349-L377)。

测试用例印证了多层嵌套的行为:

  • test_nested_blueprintgrandchild -> child -> parent 三层嵌套,URL 逐层拼接为 /parent/child/grandchild/;错误处理器按"子级优先"匹配——/parent/child/grandchild/no 触发 grandchild 的 403 处理器,而 /parent/child/no 由于 child 没有 403 处理器,回退到 parent 的处理器返回 "Parent no";
  • test_nested_callback_orderbefore_requestapp -> parent -> child 的顺序执行(teardown_request 逆序),context_processor 后注册的蓝图覆盖先注册的(模板中 {{ key }} 输出 "child")。

注意嵌套前缀的覆盖规则:注册时传入的 url_prefix 优先于蓝图构造时的默认值。参数化测试 test_nesting_url_prefixes 覆盖了"构造时定前缀"与"注册时定前缀"四种组合。

六、Blueprint 资源

资源目录(root_path)

和应用一样,蓝图被视为位于某个文件夹内。这个文件夹由 Blueprint 的第二个参数(通常是 __name__)推断:如果它指向一个真正的 Python 包,则该包目录就是资源目录;如果是模块,则取模块所在的包目录。从源码看,这一推断发生在 Scaffold 基类中,通过 get_root_path(self.import_name) 完成(见 scaffold.py#L95-L100),可用 Blueprint.root_path 属性查看结果:

>>> simple_page.root_path
'/Users/username/yourproject/yourapplication'

Blueprint.open_resource 可用于以该目录为基准打开文件:

with simple_page.open_resource('static/style.css') as f:
    code = f.read()

静态文件

admin = Blueprint('admin', __name__, static_folder='static')

路径可以是绝对路径,也可以相对于蓝图位置。默认以路径最右侧一段作为网络暴露路径,可用 static_url_path 修改。若蓝图有 url_prefix,静态文件就在 url_prefix + /static 下,例如前缀为 /admin 时,静态 URL 是 /admin/static。端点名为 blueprint_name.static,可用 url_for('admin.static', filename='style.css') 生成 URL。

注册静态路由的时机在 Blueprint.register 内:若 has_static_folder 为真,就执行 state.add_url_rule(f"{static_url_path}/<path:filename>", view_func=self.send_static_file, endpoint="static")(见 blueprints.py#L323-L328)。

一个重要限制:如果蓝图没有 url_prefix,其静态目录不可访问——因为 URL 会变成 /static,与应用自身的 /static 路由冲突且应用路由优先。与模板目录不同,蓝图静态目录不会参与缺失文件的回退搜索。仓库中的应用示例 tests/test_apps/blueprintapp/apps/admin/init.py 与测试 test_templates_and_static 演示了 url_prefix="/admin"/admin/static/test.txt/admin/static/css/test.css 的可访问性及 url_for("admin.static", filename="test.txt") 的结果。

模板

admin = Blueprint('admin', __name__, template_folder='templates')

对静态文件而言,路径可以是绝对路径或相对于蓝图资源目录;模板目录同理(template_folder 最终被包装成 FileSystemLoader(root_path + template_folder),见 scaffold.py#L280)。

模板目录的搜索优先级规则:

  • 蓝图模板目录被加入模板搜索路径,但优先级低于应用的模板目录——这样应用可以方便地覆盖蓝图提供的模板;
  • 这意味着:如果不想让蓝图模板被意外覆盖,就确保没有其他蓝图或应用模板使用相同的相对路径;多个蓝图提供相同相对路径时,先注册的蓝图优先

目录组织惯例:如果你的蓝图位于 yourapplication/admin,要渲染 'admin/index.html'template_folder='templates',需创建 yourapplication/admin/templates/admin/index.html。多出一层 admin 目录,就是为了避免被应用模板目录中同名 index.html 覆盖。推荐布局:

yourpackage/
    blueprints/
        admin/
            templates/
                admin/
                    index.html
            __init__.py

渲染时用 admin/index.html 作为查找名。排查模板加载问题时,可开启 EXPLAIN_TEMPLATE_LOADING 配置,让 Flask 在每次 render_template 时打印定位模板的完整过程。

七、构建 URL

跨页面链接用 url_for,只是端点前要加蓝图名加一个点:

url_for('admin.index')

在蓝图自身的视图函数或渲染的模板中,如果想链接到同一蓝图内的其他端点,可以用相对引用——端点前只写一个点:

url_for('.index')

例如当前请求分派到 admin 蓝图的任意端点时,它会链接到 admin.index

一个实战技巧(来自 test_blueprint_url_processors):把 url_prefix 设为含变量的形式(如 url_prefix="/<lang_code>"),配合蓝图级 url_defaultsurl_value_preprocessor,可以让蓝图内所有 URL 自动携带语言代码并自动解析回 flask.g,而视图间跳转只需 url_for('.about')

八、蓝图错误处理器

蓝图和应用一样支持 errorhandler 装饰器,可以方便地实现蓝图专属的自定义错误页:

@simple_page.errorhandler(404)
def page_not_found(e):
    return render_template('pages/404.html')

多数错误处理器工作如预期,但 404 与 405 有一个重要陷阱:蓝图注册的这两个处理器只会被"另一个同蓝图视图函数中的 raiseabort 调用"触发,而不会因非法 URL 直接访问而触发。原因在于蓝图并不"拥有"某个 URL 空间——面对一个无效 URL,应用实例无从判断应该执行哪个蓝图的 404 处理器。

注册时这些处理器会按 f"{name}.{code}" 的键合并进应用的 error_handler_spec(见 _merge_blueprint_funcs),这决定了"子蓝图优先、父蓝图回退"的查找顺序(嵌套测试 test_nested_blueprint 中有完整验证)。

如果确实想按 URL 前缀对 404/405 采用不同策略,应在应用级处理器里借助 request 代理判断:

@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

更多错误处理细节见 错误处理文档

九、速查:应用级与蓝图级装饰器的分工

蓝图还提供了一组 app_* 装饰器(blueprints.py#L613-L692),用于"借蓝图之手"向整个应用注册回调,全部基于 record_once

蓝图级装饰器 作用范围 等价于
before_request / after_request / teardown_request 仅本蓝图视图
before_app_request / after_app_request / teardown_app_request 所有请求 Flask.before_request
errorhandler 蓝图内异常(404/405 例外见上文)
app_errorhandler 所有请求 Flask.errorhandler
context_processor / app_context_processor 蓝图视图渲染 / 全部视图 Flask.context_processor
app_template_filter / app_template_test / app_template_global 全部模板 Flask.template_filter
url_value_preprocessor / app_url_value_preprocessor 蓝图 URL / 全部 URL Flask.url_value_preprocessor
url_defaults / app_url_defaults 蓝图 URL 生成 / 全部 URL Flask.url_defaults

十、小结

  • 蓝图是"延迟记录 + 注册时执行"的机制:装饰器只写入 deferred_functions,真正落地发生在 app.register_blueprint 触发的 Blueprint.register 中;
  • 端点以 蓝图名.函数名 命名,嵌套蓝图逐层加前缀;URL 前缀、子域与默认值均可在注册时用 url_prefix=subdomain=url_defaults=name= 覆盖构造时设置;
  • 静态文件目录需配合 url_prefix 才可访问;模板目录优先级低于应用目录,且同名相对路径下先注册者优先;
  • 404/405 的蓝图处理器不响应无效 URL,需要时请在应用级用 request.path 分流;
  • 蓝图一经注册便不可反注册,且注册后不能再调用 setup 方法——所有装饰器代码务必写在 register_blueprint 之前。
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

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