蓝图(Blueprint)
当应用只有一个文件时,所有路由写在一起还没问题。
但随着功能增长——用户模块、文章模块、管理后台——代码会变得难以维护。
Blueprint(蓝图)就是 Flask 提供的模块化解决方案,让你将应用拆分为独立的功能单元。
为什么需要蓝图
蓝图解决的核心问题:
- 代码组织:将相关的路由、模板、静态文件归为一组
- 复用性:同一个蓝图可以注册到不同应用,或注册多次(加不同前缀)
- 团队协作:不同开发者负责不同的蓝图模块,减少冲突
创建蓝图
蓝图使用 Blueprint 类创建,用法和 Flask 非常相似——同样的路由装饰器、同样的视图函数写法:
实例
from flask import Blueprint, render_template, request, session, redirect, url_for
# 创建蓝图实例
# 第一个参数 "auth" 是蓝图的名称(用于 url_for 引用)
# __name__ 告诉蓝图从哪里查找模板和静态文件
bp = Blueprint("auth", __name__)
# 蓝图中使用 bp.route,而不是 app.route
@bp.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
username = request.form.get("username", "")
if username:
session["username"] = username
return redirect(url_for("blog.index"))
return render_template("auth/login.html")
@bp.route("/register", methods=["GET", "POST"])
def register():
if request.method == "POST":
username = request.form.get("username", "")
if username:
return redirect(url_for("auth.login"))
return render_template("auth/register.html")
@bp.route("/logout")
def logout():
session.clear()
return redirect(url_for("blog.index"))
实例
from flask import Blueprint, render_template
bp = Blueprint("blog", __name__)
@bp.route("/")
def index():
# 模拟文章数据
posts = [
{"title": "Flask 入门", "author": "runoob"},
{"title": "Blueprint 详解", "author": "RUNOOB"},
]
return render_template("blog/index.html", posts=posts)
@bp.route("/create", methods=["GET", "POST"])
def create():
return render_template("blog/create.html")
注册蓝图
蓝图创建后不会自动生效,需要在应用中注册:
实例
from flask import Flask
from auth import bp as auth_bp
from blog import bp as blog_bp
app = Flask(__name__)
app.secret_key = "dev-secret-key"
# 注册蓝图到应用
# url_prefix:为蓝图中所有路由添加统一的前缀
app.register_blueprint(auth_bp, url_prefix="/auth")
# auth 蓝图的路由变成:/auth/login, /auth/logout, /auth/register
app.register_blueprint(blog_bp)
# blog 蓝图不带前缀,路由保持:/、/create
注册后的完整路由表:
| URL | 蓝图 | endpoint(url_for 使用) |
|---|---|---|
| / | blog | blog.index |
| /create | blog | blog.create |
| /auth/login | auth | auth.login |
| /auth/register | auth | auth.register |
| /auth/logout | auth | auth.logout |
注册蓝图后,url_for() 中的 endpoint 需要加上蓝图名前缀,例如 url_for("auth.login") 而非 url_for("login")。
url_for 在蓝图中的特殊用法
同一个蓝图中,可以使用点号开头的相对引用:
实例
@bp.route("/")
def index():
# 同蓝图内的相对引用(以 . 开头)
login_url = url_for(".login") # 等价于 url_for("auth.login")
register_url = url_for(".register") # 等价于 url_for("auth.register")
# 跨蓝图引用需要使用完整 endpoint
blog_url = url_for("blog.index") # 引用 blog 蓝图的 index
蓝图的模板和静态文件
蓝图也可以拥有自己的模板和静态文件。
当蓝图有自己的模板文件夹时,Flask 会先在应用的 templates 中查找,找不到再去蓝图指定的文件夹查找。
实例
bp = Blueprint("auth", __name__, template_folder="templates")
# 蓝图特有的模板组织
# 路径:auth/templates/auth/login.html
# 渲染时:render_template("auth/login.html")
# 这样组织可以避免文件名冲突(login.html 在 auth 和 blog 中可能是不同的)
推荐的蓝图目录结构:
myflaskapp/
├── app.py # 应用入口,注册蓝图
├── auth.py # 认证蓝图
├── blog.py # 博客蓝图
├── templates/ # 应用级模板(公共 base.html 等)
│ └── base.html
├── static/ # 应用级静态文件
│ └── style.css
├── auth/ # auth 蓝图的独立资源(可选)
│ └── templates/
│ └── auth/
│ ├── login.html
│ └── register.html
└── blog/ # blog 蓝图的独立资源(可选)
└── templates/
└── blog/
├── index.html
└── create.html
最简单的做法是不给蓝图配置独立模板文件夹,模板统一放在项目根目录的 templates/ 下,用子目录区分:templates/auth/login.html、templates/blog/index.html。
工厂模式——create_app
随着蓝图变多,直接在模块顶层创建 app = Flask(__name__) 会遇到循环导入问题。
工厂模式(Factory Pattern)是解决这个问题的最佳实践:
实例
from flask import Flask
def create_app():
"""应用工厂函数——返回配置好的 Flask 实例"""
app = Flask(__name__)
app.secret_key = "dev-secret-key"
# 加载配置
app.config.from_pyfile("config.py", silent=True)
# 在函数内部导入蓝图,避免循环引用
from auth import bp as auth_bp
from blog import bp as blog_bp
# 注册蓝图
app.register_blueprint(auth_bp, url_prefix="/auth")
app.register_blueprint(blog_bp)
return app
工厂模式的优势:
- 消除循环导入:蓝图可以导入 app,app 也可以导入蓝图,因为导入发生在函数内部
- 支持测试:可以创建不同配置的 app 实例进行测试
- 多实例部署:同一份代码可以创建多个独立的 app 实例
使用工厂模式后,需要通过 flask --app "app:create_app()" run 启动:
$ flask --app "app:create_app()" run
蓝图中的其他功能
蓝图也支持 before_request、errorhandler 等装饰器,用法和 app 一致:
实例
def check_login():
"""在 auth 蓝图的所有请求前检查登录状态"""
if request.endpoint not in ("auth.login", "auth.register"):
if "username" not in session:
return redirect(url_for("auth.login"))
@bp.errorhandler(404)
def not_found(error):
"""auth 蓝图专用的 404 错误页面"""
return render_template("auth/404.html"), 404
蓝图的 before_request 只影响该蓝图内的路由。如果你需要在所有请求前执行逻辑,应该用 app.before_request。
