蓝图

蓝图(Blueprint)用于把路由、模板、静态文件和错误处理器按功能模块组织,是 Flask 项目从单文件走向工程化的关键工具。蓝图本身不是应用,注册到应用后其内容才生效,因此同一套蓝图也可以复用到多个应用实例。

定义蓝图

# app/blog/views.py
from flask import Blueprint, render_template

bp = Blueprint("blog", __name__, url_prefix="/blog")

@bp.get("/")
def list_posts():
    return render_template("blog/list.html")

@bp.get("/<int:post_id>")
def post_detail(post_id):
    return render_template("blog/detail.html", post_id=post_id)

第一个参数 "blog" 是蓝图名,决定端点命名空间;url_prefix 让该蓝图所有路由自动带上 /blog 前缀。

注册蓝图

# app/__init__.py
from flask import Flask

def create_app():
    app = Flask(__name__)

    from .blog.views import bp as blog_bp
    from .auth.views import bp as auth_bp
    app.register_blueprint(blog_bp)
    app.register_blueprint(auth_bp, url_prefix="/auth")  # 也可注册时指定前缀

    return app

在工厂函数内部导入并注册(延迟导入),可以避免循环导入问题。

端点命名空间

蓝图中的端点名为 蓝图名.视图名

url_for("blog.list_posts")            # /blog/
url_for("blog.post_detail", post_id=3)  # /blog/3
url_for(".post_detail", post_id=3)    # 蓝图内部可用相对写法

蓝图自带模板与静态文件

bp = Blueprint(
    "blog", __name__,
    template_folder="templates",     # app/blog/templates/
    static_folder="static",          # app/blog/static/
    static_url_path="/blog-static",
)

蓝图模板目录的优先级低于应用全局 templates/。惯例是在蓝图模板目录里再嵌套一层同名子目录(templates/blog/list.html)避免命名冲突。

蓝图级钩子与错误处理

@bp.before_request           # 仅对本蓝图的请求生效
def require_login():
    if "uid" not in session:
        return redirect(url_for("auth.login"))

@bp.errorhandler(ValueError) # 仅捕获本蓝图视图抛出的异常
def bad_value(e):
    return {"error": str(e)}, 400

@bp.before_app_request       # 带 app_ 前缀:对全应用生效
def load_current_user():
    g.user = ...

注意:404 这类在路由匹配阶段就产生的错误不属于任何蓝图,只能由应用级 errorhandler 处理。

嵌套蓝图

Flask 2.0+ 支持蓝图嵌套,适合构建带版本的 API:

api = Blueprint("api", __name__, url_prefix="/api")
v1 = Blueprint("v1", __name__, url_prefix="/v1")

api.register_blueprint(v1)
app.register_blueprint(api)
# 端点:api.v1.xxx,URL:/api/v1/...

组织建议

  • 按业务域拆分蓝图(auth、blog、admin、api),而不是按技术层。
  • 每个蓝图一个包,内含 views.pymodels.pyforms.py 等。
  • 蓝图保持对应用对象零依赖(不要 from app import app),只依赖 current_app