项目小的时候,一个 app.py 放几个路由没问题。但当你有用户模块、文章模块、管理后台时,几百个路由堆在一个文件里:找路由难、改一处影响全局、团队协作冲突。蓝图(Blueprint)解决这个问题——把应用拆成"功能模块",每个模块独立管理自己的路由。
💡 关键直觉:蓝图 = 应用的"乐高积木"。每个积木(蓝图)是一块独立功能(用户、文章、后台),主应用把它们拼起来。蓝图本身不是应用,注册到应用后才生效。
# users.py from flask import Blueprint # 第一个参数是蓝图名,第二个是模块名 users_bp = Blueprint('users', __name__) @users_bp.route('/list') def user_list(): return "用户列表" @users_bp.route('/<int:user_id>') def user_detail(user_id): return f"用户 {user_id} 详情"
# app.py from flask import Flask from users import users_bp app = Flask(__name__) app.register_blueprint(users_bp, url_prefix='/users')
注册后:/users/list 和 /users/123 生效。url_prefix='/users' 给蓝图内所有路由加前缀。
admin_bp = Blueprint('admin', __name__, template_folder='templates/admin', static_folder='static/admin') @admin_bp.route('/dashboard') def dashboard(): # 模板从蓝图自己的模板目录找 return render_template('admin/dashboard.html')
资源隔离:蓝图可以指定自己的模板与静态文件目录,避免与主应用冲突。
from flask import Blueprint, url_for users_bp = Blueprint('users', __name__) @users_bp.route('/<int:user_id>') def user_detail(user_id): return f"用户 {user_id}" @users_bp.route('/list') def user_list(): # 蓝图内引用自己的视图 link = url_for('users.user_detail', user_id=42) return f"链接: {link}"
url_for 需要带蓝图名前缀:users.user_detail(蓝图名.视图函数名)。这避免了不同蓝图同名视图的冲突。
myapp/ ├── app.py # 入口:创建应用、注册蓝图 ├── config.py # 配置 ├── models/ # 数据模型(第三章 SQLAlchemy) ├── views/ │ ├── __init__.py │ ├── users.py # 用户蓝图 │ ├── posts.py # 文章蓝图 │ └── admin.py # 后台蓝图 ├── templates/ │ ├── users/ │ ├── posts/ │ └── admin/ ├── static/ └── requirements.txt
模块化原则:按"功能域"(用户/文章/后台)而不是"技术层"(路由/模板)划分——一个功能的所有代码放一起。
⚠️ 常见坑:蓝图名重复。注册多个同名蓝图会报错或互相覆盖。蓝图名全局唯一,用"功能域名"(users/posts/admin)最清晰。
# posts.py from flask import Blueprint, url_for posts_bp = Blueprint('posts', __name__) @posts_bp.route('/') def index(): return "文章列表" @posts_bp.route('/<int:post_id>') def detail(post_id): return f"文章 {post_id}"
# app.py from flask import Flask from users import users_bp from posts import posts_bp app = Flask(__name__) app.register_blueprint(users_bp, url_prefix='/users') app.register_blueprint(posts_bp, url_prefix='/posts') if __name__ == '__main__': app.run(debug=True)
访问 /users/list、/posts/5——两个模块独立工作,互不干扰。这就是蓝图的价值:功能拆分、前缀隔离、团队并行开发。
真实项目常用"应用工厂"(create_app)模式:函数创建并配置应用,蓝图在其中注册:
# app.py from flask import Flask def create_app(config_name='default'): app = Flask(__name__) app.config.from_object(config[config_name]) # 注册蓝图 from views.users import users_bp from views.posts import posts_bp app.register_blueprint(users_bp, url_prefix='/users') app.register_blueprint(posts_bp, url_prefix='/posts') return app app = create_app()
工厂模式的价值:同一份代码可创建多个配置的应用(测试配置、生产配置);蓝图在这里集中注册,结构清晰。这是第四章"应用结构最佳实践"的核心模式。
问:蓝图里的模板找不到?
检查 template_folder 是否指定且路径正确。蓝图模板目录是"相对蓝图文件"的路径。
问:两个蓝图有相同路由会冲突吗?
路由前缀不同则不冲突(/users/list vs /posts/list);前缀相同会冲突,后注册覆盖先注册。
问:蓝图能做 before_request 吗?
能。@users_bp.before_request 只对该蓝图生效——模块级钩子,适合权限校验。
问:一定要用蓝图吗?
小项目(几个路由)不必;路由多了(几十个以上)强烈建议。"规模到一定程度,蓝图是必需品"。
应用拆开了,但"出错时用户看到什么"还没设计。下一节看错误处理——把 404/500 变成友好的页面。
蓝图除了基础的分组注册,还有几个在大型项目中频繁使用的进阶特性。
第一,URL 前缀与子域名。 url_prefix 给蓝图下所有路由统一加前缀,这是最常用的模块化手段。此外 Flask 还支持通过 subdomain 参数把蓝图绑定到子域名(如 admin.example.com 指向 admin 蓝图)——这在企业级应用中用于"前台/后台"分离非常自然。实际项目里,url_prefix 的使用频率远高于子域名,先掌握前者。
第二,蓝图内的静态文件与模板。 每个蓝图可以有自己的 templates 与 static 目录。注册时通过 template_folder 和 static_folder 指定。当多个蓝图有同名模板时,Flask 按注册顺序查找——为了避免命名冲突,蓝图内模板最好放到以蓝图名命名的子目录下(如 templates/auth/login.html),这也是第四章 4.1 结构实践的标准做法。
第三,蓝图级钩子。 before_request 等钩子不仅能注册在 app 上,也能注册在蓝图上——只对该蓝图下的路由生效。这是"权限隔离"的利器:admin 蓝图注册一个"校验管理员身份"的 before_request,其他蓝图不受影响。注意 Flask 3.x 中蓝图级 before_request 需要在蓝图内显式使用,且与 app 级钩子的执行顺序有约定(app 级先执行)。
第四,蓝图与错误处理。 蓝图可以注册自己的 errorhandler,只处理该蓝图内抛出的错误。比如 admin 蓝图的 403 页面可以长得和前台不同。全局性的错误(404)通常在 app 级统一处理。
第五,蓝图拆分粒度的把握。 蓝图不是越细越好。经验法则:按"业务域"拆分(auth、blog、admin、api),而不是按"技术层"拆分(views、models、utils 各自一个蓝图)。一个业务域内部的多个视图函数,放在同一个蓝图中管理即可。当某个蓝图内部路由过多(超过几十个)时,再考虑按子功能继续拆分。
第六,蓝图与工厂模式的配合。 这是第三章之后的标准姿势:扩展对象(db、login_manager)在 extensions.py 创建,蓝图在工厂函数 create_app 内部注册。蓝图里通过 from ..extensions import db 使用扩展,避免了循环导入。第四章 4.1 会把这个结构完整演示。
一个判断标准:如果你发现自己在"为拆分而拆分"、蓝图之间互相引用严重,那说明拆分粒度不对——先合并,再按真实业务边界重拆。模块化的目的是降低耦合,而不是制造更多的文件。