首页
/ Flask 应用工厂模式详解:用 create_app() 构建可测试、可配置的 Flask 应用

Flask 应用工厂模式详解:用 create_app() 构建可测试、可配置的 Flask 应用

2026-09-04 14:48:27作者:晏闻田Solitary

Flask 应用本质上是一个 Flask 类的实例,配置、路由等一切都注册在这个实例上。本文基于官方教程的 Application Setup 章节(docs/tutorial/factory.rst),系统讲解“应用工厂(application factory)”模式:为什么不在模块顶层创建全局 Flask 实例、如何在 create_app() 中完成实例创建与配置加载、instance folder 机制如何隔离本地数据,以及如何用 flask --app flaskr run --debug 启动并验证应用。读完本文,你将掌握应用工厂的完整写法,并理解其中每个参数在 Flask 源码中的实际作用。

为什么需要应用工厂

最直接的 Flask 应用写法是在代码顶部直接创建一个全局 Flask 实例——"Hello, World!" 示例就是这么做的。这种方式简单、在小型项目中完全够用,但随着项目增长会暴露一些问题:

  • 全局实例在导入时即被创建,配置无法在创建前注入,导致测试时难以隔离(每个测试都想用独立的临时数据库,但全局实例只能有一份配置);
  • 同一个应用难以按环境(开发/测试/生产)切换不同配置;
  • 多实例部署或扩展初始化时,全局对象容易产生隐式依赖。

Flask 官方的解决方案是:不在全局创建 Flask 实例,而是在一个函数内创建它。这个函数就是应用工厂(application factory),应用所需的全部配置、注册和初始化都在函数体内完成,最后返回 app

创建 flaskr 包与应用工厂函数

创建 flaskr 目录并添加 __init__.py 文件。__init__.py 身兼两职:既容纳应用工厂函数,又告诉 Python 将 flaskr 目录当作一个包处理。

$ mkdir flaskr
# flaskr/__init__.py
import os

from flask import Flask


def create_app(test_config=None):
    # create and configure the app
    app = Flask(__name__, instance_relative_config=True)
    app.config.from_mapping(
        SECRET_KEY='dev',
        DATABASE=os.path.join(app.instance_path, 'flaskr.sqlite'),
    )

    if test_config is None:
        # load the instance config, if it exists, when not testing
        app.config.from_pyfile('config.py', silent=True)
    else:
        # load the test config if passed in
        app.config.from_mapping(test_config)

    # ensure the instance folder exists
    os.makedirs(app.instance_path, exist_ok=True)

    # a simple page that says hello
    @app.route('/hello')
    def hello():
        return 'Hello, World!'

    return app

create_app 就是应用工厂函数。教程的后续章节会继续向它添加数据库命令和蓝图,但此时它已经完成了大量核心工作。下面逐步解析。

1. Flask(__name__, instance_relative_config=True):创建实例

  • __name__ 是当前 Python 模块的名字。Flask 需要知道应用位于何处以便推导若干路径(如 root_path),__name__ 就是最方便的告知方式。在 __init__.py 中它即 "flaskr"
  • instance_relative_config=True 告诉应用:相对路径的配置文件以 instance folder(实例文件夹)为基准,而不是以应用根目录为基准。

源码印证:该参数在 sansio/app.py 中的定义为——“if set to True relative filenames for loading the config are assumed to be relative to the instance path instead of the application root.”;而 instance_path 参数默认值则是“the folder 'instance' next to the package or module”(包/模块旁边的 instance 文件夹)。

instance folder 位于 flaskr外部,专门存放不应提交到版本控制的本地数据,例如配置密钥和数据库文件。关于 instance folder 的完整说明见官方配置文档 docs/config.rst

2. app.config.from_mapping():设置默认配置

from_mapping 写入应用将使用的默认配置项:

配置项 取值 说明
SECRET_KEY 'dev' Flask 和扩展用它保证数据安全(如签名 cookie)。'dev' 只是开发期方便用的占位值,部署时必须用随机值覆盖
DATABASE os.path.join(app.instance_path, 'flaskr.sqlite') SQLite 数据库文件的存放路径,位于 app.instance_path(即 Flask 为实例文件夹计算出的路径)下

从源码看,from_mapping 等价于 update 但会忽略非大写键(见 config.py),所以上下文中的键都必须是全大写——这也是 Flask 配置的约定。

3. app.config.from_pyfile('config.py', silent=True):加载实例配置

如果 instance folder 中存在 config.py,它覆盖上面的默认值。典型用途是部署时设置真正的 SECRET_KEY。例如 instance folder 中的 config.py

SECRET_KEY = "development-key"  # 部署时替换为随机字符串

silent=True 的含义可从 config.py 的实现确认:from_pyfile 内部以 exec 执行配置模块后调用 from_object;当文件不存在(ENOENT/EISDIR/ENOTDIR)且 silent 为真时静默返回 False,因此“instance 配置不存在”不是错误,开发环境可以直接跑起来。

test_config 分支:也可以向工厂传入 test_config 字典,它会被 app.config.from_mapping(test_config)(仓库示例代码中写作 app.config.update(test_config),两者对 dict 等价)加载,替代实例配置。这是为了让教程后续编写的测试独立于你本地的开发配置。

4. os.makedirs(app.instance_path, exist_ok=True):确保实例文件夹存在

Flask 不会自动创建 instance folder,但项目稍后要把 SQLite 数据库文件写在那里,所以工厂函数显式创建它。exist_ok=True 保证文件夹已存在时不报错。

5. @app.route('/hello'):一个简单路由

创建一个 URL /hello 到视图函数的映射,返回字符串 'Hello, World!'。它让你在教程继续深入之前就能亲眼看到应用跑起来。

运行应用

在终端用 flask 命令运行应用——告诉 Flask 去哪里找到应用,并开启 debug 模式。注意:此时你应处于顶层项目目录(如 flask-tutorial)下,而不是 flaskr 包内部

$ flask --app flaskr run --debug

Debug 模式会在页面抛出异常时展示交互式调试器,并在代码改动后自动重启服务器。你可以让它一直运行,跟随教程只需刷新浏览器页面即可。

正常输出类似:

 * Serving Flask app "flaskr"
 * Debug mode: on
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: nnn-nnn-nnn

在浏览器访问 http://127.0.0.1:5000/hello,应看到 "Hello, World!" 消息。

端口被占用的处理:如果 5000 端口已被其他程序占用,服务器启动时会报 OSError: [Errno 98](Linux)或 OSError: [WinError 10013](Windows)。处理方法(改用 --port 指定其他端口)见 CLI 文档 docs/cli.rst

用测试验证工厂行为

仓库中的 tutorial 示例项目(examples/tutorial)提供了可直接参考的完整实现与测试,印证了工厂模式“测试注入配置”的设计意图。

单元测试直接验证工厂的两种调用路径(test_factory.py):

def test_config():
    """Test create_app without passing test config."""
    assert not create_app().testing
    assert create_app({"TESTING": True}).testing


def test_hello(client):
    response = client.get("/hello")
    assert response.data == b"Hello, World!"

而 pytest 的 fixture 正是靠 test_config 参数,为每个测试构造一个使用临时数据库的独立应用(conftest.py):

@pytest.fixture
def app():
    """Create and configure a new app instance for each test."""
    # create a temporary file to isolate the database for each test
    db_fd, db_path = tempfile.mkstemp()
    # create the app with common test config
    app = create_app({"TESTING": True, "DATABASE": db_path})

    # create the database and load test data
    with app.app_context():
        init_db()
        get_db().executescript(_data_sql)

    yield app

    # close and remove the temporary database
    os.close(db_fd)
    os.unlink(db_path)

这就是工厂模式的核心收益:create_app() 每次调用都返回全新实例,测试彼此隔离,且完全不受开发者 instance folder 中配置的影响。

完整示例中的后续扩展

教程示例最终演进的工厂函数(examples/tutorial/flaskr/init.py)在本文讲解的基础上又增加了三步,展示了工厂作为“集中注册点”的扩展方式:

def create_app(test_config=None):
    """Create and configure an instance of the Flask application."""
    app = Flask(__name__, instance_relative_config=True)
    app.config.from_mapping(
        # a default secret that should be overridden by instance config
        SECRET_KEY="dev",
        # store the database in the instance folder
        DATABASE=os.path.join(app.instance_path, "flaskr.sqlite"),
    )

    if test_config is None:
        app.config.from_pyfile("config.py", silent=True)
    else:
        app.config.update(test_config)

    os.makedirs(app.instance_path, exist_ok=True)

    @app.route("/hello")
    def hello():
        return "Hello, World!"

    # register the database commands
    from . import db
    db.init_app(app)

    # apply the blueprints to the app
    from . import auth
    from . import blog
    app.register_blueprint(auth.bp)
    app.register_blueprint(blog.bp)

    # make url_for('index') == url_for('blog.index')
    app.add_url_rule("/", endpoint="index")

    return app

注意两个值得学习的细节:db.init_app(app) 展示了扩展“两阶段初始化”(扩展模块不持有全局 app,而是由工厂把 app 传给它),以及 register_blueprint 集中注册蓝图。从源码结构看,这些调用全部发生在工厂函数内,意味着任何实例都获得了相同的注册结果,而配置可以按实例不同——这正是应用工厂模式相对全局实例的本质优势。

小结

  • Flask 应用是 Flask 实例,应用工厂函数 create_app() 是该实例的统一创建点:创建实例、设置默认配置、加载环境配置、确保 instance folder 存在、注册路由,最后返回 app
  • instance_relative_config=True + instance folder 把“不应入库的本地数据”(密钥、SQLite 数据库)隔离到包外,from_pyfile('config.py', silent=True) 允许在部署时静默覆盖默认值;
  • 传入 test_config 的分支让测试以独立配置构造应用,仓库中的 conftest.pytest_factory.py 是该机制的完整落地;
  • 运行入口为 flask --app flaskr run --debug,端口冲突时按 docs/cli.rst 的说明换端口即可。
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.79 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
988
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384