不能直接用全局变量存请求数据,因为模块级变量在多线程/协程下被所有请求共享,导致数据覆盖和脏读;Flask 的 request 和 g 是 Context Local 对象,基于 threading.local 或 contextvars 实现线程/协程隔离,生命周期严格绑定单次请求。

为什么不能直接用全局变量存请求数据
Flask 的 request 对象看似是全局的,但实际是 Context Local —— 它在每个请求中隔离,不会被其他并发请求污染。如果用普通全局变量(比如 current_user = None)来存用户信息,多线程或异步 worker 下会互相覆盖,出现 A 用户看到 B 用户的数据这种严重问题。
根本原因:Python 的模块级变量是进程内共享的,而 Flask 的请求上下文(RequestContext)靠 werkzeug.local.Local 实现线程/协程局部存储,底层依赖 threading.local() 或 contextvars.ContextVar(取决于运行环境)。
- 同步部署(如 Gunicorn + sync workers):依赖
threading.local - 异步部署(如 Uvicorn + ASGI):Flask 2.3+ 自动降级到
contextvars,但需确保app.run()不用于生产 - 手动创建上下文(如测试或 CLI):必须显式调用
app.test_request_context()或app.request_context()
如何安全地扩展 request 上下文(推荐用 g + before_request)
Flask 提供了 g(flask.g)这个 Context Local 对象,专为单个请求生命周期内临时存储数据设计。它比自己 new 一个 Local 更轻量、更符合 Flask 习惯。
典型用法是在 before_request 中初始化所需对象,并在请求结束前清理(可选):
立即学习“Python免费学习笔记(深入)”;
SkillSub Pro - Python 题解与代码注释双功能技能功能概述SkillSub Pro - Python 题解与代码注释双功能技能是一项面向实际任务的技能,主要用于SkillSub Pro 是一个 Python 题解生成与代码注释的 双功能合体技能 ,专为学生、算法学习者和开发者设计;✅ 一个技能,两种用途 :;核心要点📝 题解模式 :输入题目/题号,自动生成完整 Python 题解(含详细注释、解题思路、复杂度分析);💬 注释模式 :输入 Python 代码,自动添加详细中。它将相关步骤、
@app.before_request
def load_user():
user_id = request.headers.get("X-User-ID")
if user_id:
g.user = User.query.get(user_id)
else:
g.user = None
<p>@app.route("/api/profile")
def profile():
if not g.user:
return {"error": "Unauthorized"}, 401
return {"name": g.user.name}
-
g只在当前请求上下文中有效,函数返回后自动丢弃,无需手动清空 - 不要在
g中存大对象(如数据库连接),应使用teardown_appcontext管理资源释放 - 避免在
g中存可变对象并跨函数修改,容易引发状态混乱
什么时候该自己用 Local 而不是 g
绝大多数场景用 g 就够了。只有当你需要跨多个 Flask 应用共享同一套上下文逻辑(比如封装成独立中间件),或要绕过 Flask 生命周期控制时,才考虑直接操作 werkzeug.local.Local。
例如,在自定义日志中间件中统一注入 trace_id,且不希望和业务代码的 g 冲突:
from werkzeug.local import Local _request_local = Local() <h1>在 WSGI middleware 中设置</h1><p>def tracing_middleware(app): def inner(environ, start_response): _request_local.trace_id = generate_trace_id() return app(environ, start_response) return inner</p><h1>在日志 handler 中读取</h1><p>def log_with_trace(): try: return _request_local.trace_id except RuntimeError: # 没有活动上下文 return "N/A"
- 直接用
Local会绕过 Flask 的上下文栈管理,teardown_request不会自动触发它的清理 - 必须确保
Local实例在所有线程/协程中是同一个对象(通常定义为模块级变量) - 在 ASGI 环境中,
Local默认不支持协程隔离,要用LocalStack或改用contextvars
常见报错:RuntimeError: Working outside of application context
这个错误不是因为用了 Context Local,而是你试图在没有激活 Flask 上下文的地方访问 request、g 或 current_app。比如在模块顶层、定时任务、或单元测试里直接写 g.user = ...。
- 修复方式:用
app.app_context()包裹非请求场景(如 CLI 命令) - 测试中用
app.test_request_context()模拟一次请求 - 绝对不要在
__init__.py或配置文件里引用g或request - 异步任务(如 Celery)无法继承请求上下文,必须显式传参,不能依赖
g
Context Locals 很方便,但它的“隐形”恰恰是最危险的——一旦漏掉上下文激活,错误往往延迟暴露,排查成本远高于早期加一层检查。

















