g不是全局变量而是请求上下文绑定对象,仅在当前请求生命周期内有效,请求结束即清空,本质是线程/协程隔离的LocalProxy。

为什么 g 不是全局变量却叫“全局”
g 是 Flask 提供的请求上下文绑定对象,只在当前请求生命周期内有效。它不是 Python 模块级全局变量,也不会跨请求残留——这点常被误读。一旦请求结束,g 就被清空;下个请求进来时,它又是全新的空对象。
所以你不能用 g 存用户登录态来“替代 session”,也不能指望 g.user 在异步任务或后台线程里还能访问到。
-
g本质是LocalProxy,底层靠werkzeug.local.LocalStack实现线程/协程隔离 - 在多线程部署(如 gunicorn + threads)或异步视图(
async def)中,只要还在同一个请求上下文里,g就安全可用 - 若你在
before_request里设了g.user = user,那后续所有视图、模板、自定义函数里都能直接读,不用层层传参
g.user 怎么设才不会漏掉或覆盖
最稳妥的位置是 @app.before_request,且必须做存在性判断。很多人直接写 g.user = get_current_user(),结果没登录时返回 None,后面逻辑崩了还找不到原因。
更常见问题是中间件或蓝本里重复设置,导致后设的覆盖先设的(比如认证蓝本和权限蓝本都往 g.user 写)。
- 统一入口设:只在主应用的
before_request里解析 token / cookie,查库赋值,其他地方只读不写 - 加保护:设之前先检查
hasattr(g, 'user'),避免被意外覆盖 - 设
None要明确:如果用户未登录,建议显式写g.user = None,而不是跳过,否则后续if g.user判断会因属性不存在而报AttributeError
模板里用 g.user 报 AttributeError 怎么快速定位
这通常不是模板问题,而是请求还没走到 before_request 那步就渲染了模板——比如用了 url_for 触发了未鉴权的静态资源路由,或者重定向前就提前访问了 g.user。
另一个高频原因是用了 app.add_url_rule 注册函数但没挂载到应用上下文,导致 g 根本没初始化。
- 检查报错路由是否被
@app.before_request覆盖:某些路径(如/static/、/health)可能被排除在钩子外 - 在模板开头加
{% if not g.user %}{{ g.__dict__|pprint }}{% endif %}快速看g里到底有啥 - 确认你没在
app = Flask(__name__)之前就 import 并调用了依赖g的模块
g 和 session、request 的分工边界在哪
session 存客户端可感知的、带签名的持久化数据(比如用户 ID、偏好),request 是当前 HTTP 请求的原始输入(headers、form、args),而 g 是服务端单次请求内的“工作台”——放计算结果、DB 连接、当前用户对象这类中间产物。
混用三者最典型的坑:把 g.user 当登录凭证存进 session,或者在 g 里塞大量序列化数据(比如整个用户 profile 字典),徒增内存开销。
- 该进
session的:用户 ID、语言偏好、上一次搜索关键词(需跨页保持) - 该放
g的:查出来的User模型实例、db.session对象、当前请求的权限列表(从 DB 或缓存查完后暂存) - 别碰
request的:不要往里面 setattr,它是个只读代理,强行写会静默失败或报错
复杂点在于,g 看似简单,但它和上下文生命周期绑得太紧——漏掉一个钩子、多一个线程跳转、甚至一个没 await 的 async 函数,都可能让 g.user 突然变 None。盯住请求链路,比记住语法重要得多。










