bigywater.tech
Flask · 80

自定义 Flask Extension

自己写 Flask 扩展要遵守哪些约定?连接之类的请求级资源放哪、怎么关?状态为什么不能放在扩展对象上?这篇写一个完整的最小扩展,每条约定都跑一遍。

7 / 8
状态存在扩展对象的实例属性上时,8 个并发请求读到别人值的个数
3 个
一个请求用一个懒创建的连接,请求结束时 teardown 关闭——5 个请求(3 个用连接)开关次数
4 件事
init_app 里常做的:写默认配置、登记 app.extensions、注册钩子、注册命令

扩展骨架

实测 · 本机执行输出
  核心结构(节选):
      1 | class Hello:
      2 |     def __init__(self, app=None, greeting=None):
      3 |         self._greeting = greeting
      4 |         if app is not None: self.init_app(app)
      5 |     def init_app(self, app):
      6 |         app.config.setdefault("HELLO_GREETING", "hello")
      7 |         app.extensions["hello"] = {"inst": self, "greeting": self._greeting or app.config["HELLO_GREETING"]}
      8 |         app.teardown_appcontext(self._teardown)
      9 |         ...
  同一个 hello 对象,应用 1: hello, Ann
  应用 2(HELLO_GREETING=你好): 你好, Ann
  配置里被填入的默认值: {'HELLO_GREETING': 'hello', 'HELLO_DSN': 'mem://default'}
  对同一个应用重复 init_app → Hello 已经在这个应用上初始化过了
  应用上下文之外使用 → Hello 需要应用上下文:请在 app.app_context() 里使用
  没 init_app 的应用里使用 → 这个应用没有 init_app(Hello)

上一篇:Flask-CORS;下一篇:Flask-SQLAlchemy 初始化过程。

第 71 篇讲了 init_app 的时机,这一篇把约定落成代码。

顺手注册的东西

实测 · 本机执行输出
  模板过滤器 shout: HEY!
  after_request 加的头 X-Hello: hello | 正文: hello, world
  flask hello Bob → hello, Bob (cli) (退出码 0 )
  flask --help 的 Commands: ['hello', 'limiter', 'routes', 'run', 'shell']

请求级资源与 teardown

实测 · 本机执行输出
  5 个请求(2 个不用连接、3 个用):opened=3 closed=3
  视图抛异常(HTTP 500)之后:opened=1 closed=1(teardown 照样执行)

状态放哪里

实测 · 本机执行输出
  bad  8 个并发请求(各睡 0.2 秒再读自己写的值):读到别人的值的请求 7 个
  good 8 个并发请求(各睡 0.2 秒再读自己写的值):读到别人的值的请求 0 个

并发请求用的是 gunicorn 的 gthread worker(每个请求一个线程)。

配置优先级

实测 · 本机执行输出
  构造参数 greeting="ctor" + HELLO_GREETING="cfg" → ctor, x
  只有 HELLO_GREETING="cfg" → cfg, x
  都没设 → hello, x

测试扩展

实测 · 本机执行输出
  3 个互相独立的应用的响应: ['g0, world', 'g1, world', 'g2, world']
  FakeConn 是类属性计数器——共享的状态会让测试互相污染:各用例开始前要手动清零 → opened = 0

分步演示

自定义 Flask Extension
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.7 / Flask 3.1.3 / Werkzeug 3.1.9 / Jinja2 3.1.6 / SQLAlchemy 2.1.1 / gunicorn 26.2.0(都装在自己的虚拟环境里);大多数实验用 Flask 的 test_client(直接调用 WSGI 应用,不经过网络),需要真实 HTTP 时另起 gunicorn 或开发服务器(端口 9600 以上,本机 127.0.0.1);计时用 time.perf_counter,数字每次运行会有小幅抖动。扩展是本篇自己写的 hello_ext.py,连接是一个假的 FakeConn 计数对象
    • 没有验证:打包发布到 PyPI(pyproject 与入口点)、Flask 扩展命名规范的审核流程、异步视图(async def)里使用 g 的行为(见第 136 篇)、signals(blinker)的使用
    京ICP备2026067502号-1京公网安备11010502063487号