Python 的 Flask 框架能让我们快速的建立一个轻量级的 Web 或 REST API。对于小应用由一个 @app 装饰一撸到底就行,当项目稍具规模或要更清晰就要考虑模块化,于是来到了我们今天的话题,首先是
为什么需要 Blueprint?
比如说我们一定超级简单的 Flask 应用 main.py 的代码如下:
1 2 3 4 5 6 7 8 |
from flask import Flask app = Flask(__name__) @app.route('/', methods=['GET']) def hello(): return "hello world!" |
现在需要加上一个后端管理 admin 模块,可以继续在 main.py 中加代码,并且依然用 @app 来装饰
1 2 3 4 5 6 7 8 9 10 11 12 13 |
from flask import Flask app = Flask(__name__) @app.route('/admin/user', methods=['POST']) def add_user(): return 'add user' @app.route('/', methods=['GET']) def hello(): return "hello world!" |
这只是用 URL 前缀来区分了功能,分别是
- GET /
- POST /admin/users
再进一步, 我们可在同一个模块中使用 Blueprint,main.py 的代码变为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
from flask import Flask, Blueprint app = Flask(__name__) admin = Blueprint('admin_blueprint', __name__) @admin.route('/user', methods=['POST']) def add_user(): return 'add user' @app.route('/', methods=['GET']) def hello(): return "hello world!" app.register_blueprint(admin, url_prefix='/admin') |
实现的功能与前面的 main.py 是一样的。从这里高亮行可以看出实现一个 Blueprint 需要基本的三步
- 声明一个 Blueprint
- 用 Blueprint 实例去装饰函数
- 注册 Blueprint 到 Flask 应用实例上
由 Blueprint 装饰的函数,访问时必须加上它的 url_prefix
前缀。如
@admin.route('/user', methods=['POST'])
访问时要加上 /admin
前缀,就是
POST /admin/users
注:有两种方式指定 Blueprint 的 url_prefix
- 声明 Blueprint 时可以指定 url_prefix: 相当于是默认的 url_prefix
- 向 app 注册 Blueprint 时也可以指定 url_prefix:注册时不指定就用声明时的 url_prefix, 否则会覆盖声明时的 url_prefix
Blueprint 的 url_prefix 就像是 Java 的 Spring MVC 中 Controller 类的 @RequestMapping 定义一样,定义在类级别的 @RequestMapping path 会作为 Controller 方法的 URL 前缀。
使用 Blueprint 模块化应用
上一节中我们体验了在同一个 Python 使用 Blueprint, 虽然 Blueprint 是通了,但那样不能算作真正的模块化。要实现模块化至少得把功能分到不同的 Python 文件中去,最好是有各自的目录。也就产生了两种使用 Blueprint 模块化的方式,使用模块和包
Blueprint 定义在不同的模块中
首先进行文件分家,我们要把 /admin
下的实现挪到 admin.py
文件中去,于是 main.py
和 admin.py
的内容就变成了
admin.py
1 2 3 4 5 6 7 8 |
from flask import Blueprint admin = Blueprint('admin_blueprint', __name__) @admin.route('/users', methods=['POST']) def add_user(): return 'add user' |
在 admin 中像使用 @app 一样用 @admin 装饰,并且不需要知道哪个 Flask 应用去注册它
main.py
1 2 3 4 5 6 7 8 9 10 11 12 |
from flask import Flask from admin import admin app = Flask(__name__) @app.route('/', methods=['GET']) def hello(): return "hello world!" app.register_blueprint(admin, url_prefix='/admin') |
注册第二行的引用是从模块 admin 中引用同名的 admin 变量
Blueprint 定义在不同的包(目录) 中
对于更复杂的应用,把不同功能分布到不同的模块文件中还不够,用包(目录)来组织不同的功能就显得十分的必要了。
如果在 admin 中只有一个 user 模块使用了 Blueprint,我们可以用下面的目录结构
my-project
├── admin
│ └── user.py
└── main.py
在 user.py 中
1 2 3 4 5 6 7 8 |
from flask import Blueprint admin = Blueprint('admin', __name__) @admin.route('/users', methods=['POST']) def add_user(): return 'add user' |
在 main.py 中就是
1 2 3 4 5 6 7 8 9 10 11 12 |
from flask import Flask from admin.user import admin app = Flask(__name__) @app.route('/', methods=['GET']) def hello(): return "hello world!" app.register_blueprint(admin, url_prefix='/admin') |
现在两个 API
- GET /
- POST /admin/users
再往 admin 包中添加一个 order 模块,情形就变得稍微复杂了,由 order 和 user 都必须使用同一个 Blueprint 实例,所以需要在 __init__.py 中初始化它。同时为了在 Flask 启动时加载 admin/user 和 admin/order 模块,也就要在 __init__.py 中导入它们(main 导入 admin 包时还要立即导入其下的 user 和 order 模块)。所以,重新组织后的目录结构如下
my-project
├── admin
│ ├── __init__.py
│ ├── order.py
│ └── user.py
└── main.py
每个文件的内容如下
admin/order.py
1 2 3 4 5 6 |
from admin import admin @admin.route('/orders', methods=['GET']) def list_orders(): return 'all orders' |
admin/user.py
1 2 3 4 5 6 |
from admin import admin @admin.route('/users', methods=['POST']) def add_user(): return 'add user' |
admin/__init__.py
1 2 3 4 5 6 |
from flask import Blueprint admin = Blueprint('admin', __name__) from . import user from . import order |
这个文件的内容必须在声明 Blueprint 后把使用了它的模块导入到命包空间来,否则不能注册那些 endpoints。其目的就是当我们在 main.py 中用
from admin import admin
导入 admin 包时能立即导入 admin 包中的 user 和 order 模块
如果不想在 admin/__init__.py 中逐个导入子模块,而需要把所有子模块全部自动引入的话,就在 __init__.py 中加入下方的代码
1 2 3 4 |
import pkgutil for loader, module_name, is_pkg in pkgutil.walk_packages(__path__): _module = loader.find_module(module_name).load_module(module_name) globals()[module_name] = _module |
或者
1 2 3 4 5 |
import importlib import pkgutil for loader, module_name, is_pkg in pkgutil.walk_packages(__path__, __name__ + '.'): importlib.import_module(module_name) |
IDE 可能会提示后面的 import
语句要提到前面去,这时候不能听它的,否则出现错误
ImportError: cannot import name 'admin' from partially initialized module 'admin' (most likely due to a circular import)
main.py
1 2 3 4 5 6 7 8 9 10 11 12 |
from flask import Flask from admin import admin app = Flask(__name__) @app.route('/', methods=['GET']) def hello(): return "hello world!" app.register_blueprint(admin, url_prefix='/admin') |
main.py 文件没什么变化,关键的部分应该是在 admin/__init__.py 中
现在运行 Flask,就有以下三个 API
- GET /
- GET /admin/orders
- POST /admin/users
关于 Flask Blueprint 的其他知识
更多的关于 Blueprint 的用法可参考源码中的 Bluepint 初始函数和 Flask.register_blueprint 注册函数。
由于 Blueprint 相当于一个 Flask 应用的子站点,所以它可以定义自己的静态文件映射,子域名。Blueprint 可以嵌套,也就是一个 Blueprint 还能注册另一个 Blueprint
1 2 3 4 5 |
blueprint_aa = Blueprint("aa", __name__) blueprint_bb = Blueprint("aa", __name__) blueprint_aa.register_blueprint(bb, url_prefix="/bb") app.register_blueprint(blueprint_aa, url_prefix="/aa") |
那么 blueprint_bb 中的 /user 的 API 完整路径就是
/aa/bb/user
我们或许还可以考虑把 Flask app 和 Blueprint 实例作为参数传递给别的模块使用。
链接:
本文链接 https://yanbin.blog/understand-flask-blueprint/, 来自 隔叶黄莺 Yanbin Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。