Flask 应用工厂模式是什么?为什么推荐 create_app?
简化版
Flask 应用工厂模式是用 create_app() 函数创建并配置 Flask 应用,而不是在模块导入时直接创建全局 app。它方便按环境加载配置、初始化扩展、注册蓝图、编写测试,也能避免循环导入和全局状态难管理的问题。
详细版
应用工厂通常长这样:
def create_app(config_name=None):
app = Flask(__name__)
app.config.from_object(config_map[config_name])
db.init_app(app)
migrate.init_app(app, db)
app.register_blueprint(user_bp)
app.register_blueprint(order_bp)
return app
推荐应用工厂的原因:
- 可以创建多个不同配置的 app,方便测试。
- 扩展对象可以先创建,再用
init_app延迟绑定。 - 配置加载、蓝图注册、错误处理器注册集中管理。
- 减少循环导入。
- 更适合生产、测试、开发多环境切换。
面试重点是说明:应用工厂不是语法技巧,而是 Flask 工程化组织方式。
完整版教学
一、为什么直接创建全局 app 会有问题
很多入门代码这样写:
app = Flask(__name__)
app.config["DEBUG"] = True
小项目没问题,但工程一大就会遇到几个麻烦。第一,配置在导入时已经确定,测试时很难创建不同配置的应用。第二,扩展初始化、蓝图注册、模型导入容易互相依赖,形成循环导入。第三,一个进程里如果需要创建多个 app 实例,直接全局 app 会非常不灵活。
应用工厂把“创建应用”变成一个函数调用。每次调用 create_app() 都能得到一个配置完整的 Flask 实例。这让应用创建过程更可控。
二、应用工厂的典型结构
常见项目结构:
app/
__init__.py
config.py
extensions.py
blueprints/
user.py
order.py
extensions.py 里通常不绑定 app:
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
db = SQLAlchemy()
migrate = Migrate()
__init__.py 中创建应用:
def create_app(config_object):
app = Flask(__name__)
app.config.from_object(config_object)
db.init_app(app)
migrate.init_app(app, db)
from .blueprints.user import user_bp
app.register_blueprint(user_bp)
return app
这种模式里,扩展对象先独立创建,等 app 出现后再通过 init_app 绑定。这样可以避免扩展在导入阶段就依赖某个具体 app。
三、应用工厂和配置管理
不同环境需要不同配置:开发环境开启 debug,测试环境使用测试数据库,生产环境关闭 debug 并读取安全密钥。应用工厂可以按参数或环境变量加载配置:
def create_app(config_name="development"):
app = Flask(__name__)
app.config.from_object(configs[config_name])
return app
测试时可以这样:
app = create_app("testing")
client = app.test_client()
这样每个测试可以拿到独立配置,不会污染生产配置。面试时提到测试,是应用工厂很重要的加分点。
四、应用工厂如何缓解循环导入
循环导入在 Flask 项目里很常见。比如 views 需要导入 db,app 初始化又导入 views;models 需要 app 配置,app 又需要导入 models。应用工厂通过延迟导入和 init_app 降低这种耦合。
常见做法是:
- 扩展对象放
extensions.py。 - 蓝图在
create_app()内部导入并注册。 - 模型只依赖扩展对象
db,不直接依赖具体 app。 - 需要当前 app 时使用
current_app,而不是导入全局 app。
这不是说应用工厂能自动解决所有循环导入,但它提供了更清晰的依赖方向。
五、应用工厂和部署入口
使用应用工厂后,生产部署入口可能写成:
from app import create_app
app = create_app("production")
Gunicorn 可以加载这个 app:
gunicorn "wsgi:app"
或者使用支持工厂语法的方式:
gunicorn "app:create_app()"
具体命令取决于项目结构和部署工具。关键是生产环境仍然需要一个 WSGI application 对象,应用工厂负责创建它。
六、常见误区与工程边界
误区一:所有代码都放进 create_app()。create_app 应该负责配置应用、初始化扩展、注册蓝图、注册钩子和错误处理器,不应该塞业务逻辑。
误区二:用了应用工厂但扩展仍然在导入时绑定全局 app。这样会破坏工厂模式的意义。扩展对象应优先使用 init_app 延迟绑定。
误区三:在模型或工具函数中导入 app。更推荐使用 current_app 读取配置,或通过函数参数显式传入依赖。
七、常见误区与追问
| 做法 | 是否推荐 | 原因 |
|---|---|---|
| 模块导入时直接创建全局 app | 小 demo 可以,工程慎用 | 配置和测试隔离差,容易循环导入 |
create_app(config) | 推荐 | 可按环境创建不同 app,集中注册组件 |
扩展 db.init_app(app) | 推荐 | 扩展先声明,后绑定具体 app |
- 误区:应用工厂只是把 app 创建代码换个函数名。 它的核心价值是延迟创建、配置隔离、扩展初始化、蓝图注册和测试友好。
- 误区:所有业务代码都应该塞进 create_app。
create_app应负责装配应用,不应承载订单、支付、用户注册等业务流程。 - 误区:用了工厂就不会循环导入。 如果模型、蓝图、扩展仍互相导入具体 app,循环导入仍会出现;关键是延迟绑定和清晰依赖方向。
- 追问:为什么扩展对象常放 extensions.py? 先定义
db = SQLAlchemy()等对象,再在工厂里init_app(app),能避免导入时绑定固定 app。 - 追问:测试为什么喜欢应用工厂? 测试可以创建使用测试配置、临时数据库或 mock 依赖的 app,不影响生产配置。
- 追问:生产部署如何调用应用工厂? 可以暴露
wsgi.py中的app = create_app(),也可以用 Gunicorn 支持的工厂调用语法。
记忆钩子:应用工厂不是“多一层函数”,而是把 app 的创建、配置、扩展、蓝图注册变成可重复、可测试、可切换的装配过程。
八、加强记忆
Flask 应用工厂就是用 create_app() 统一创建和配置应用。它让配置、扩展初始化、蓝图注册和测试环境切换更清晰,也能减少全局 app 带来的循环导入和状态污染问题。大型 Flask 项目通常会把应用工厂、蓝图和扩展延迟初始化配合使用。