Flask 的应用上下文和请求上下文有什么区别?
简化版
Flask 有应用上下文和请求上下文。应用上下文保存当前应用相关信息,让 current_app、g 可用;请求上下文保存当前请求相关信息,让 request、session 可用。请求上下文通常会自动伴随应用上下文一起推入,请求结束后再弹出。
详细版
Flask 中常见上下文对象:
current_app:当前 Flask 应用对象,属于应用上下文。g:一次应用上下文中的临时全局对象,常用于保存数据库连接、当前用户等临时数据。request:当前请求对象,属于请求上下文。session:当前请求的会话对象,属于请求上下文。
它们都不是普通全局变量,而是上下文局部代理。Flask 根据当前执行环境找到对应的上下文数据。
如果在没有请求或应用上下文的地方访问这些对象,会出现类似 “working outside of application context” 或 “working outside of request context” 的错误。解决方式不是随便导入 app,而是在命令行、脚本、测试、后台任务中显式推入上下文。
完整版教学
一、为什么 Flask 需要上下文
Web 服务是并发处理请求的。多个用户可能同时访问同一个接口,但视图里却可以直接写:
from flask import request
user_agent = request.headers.get("User-Agent")
如果 request 真的是普通全局变量,就会产生严重并发问题:用户 A 的请求数据可能被用户 B 覆盖。Flask 的做法是使用上下文局部对象,也就是你看到的是一个代理对象,真正的数据按当前请求隔离保存。
这套机制让代码写起来像访问全局变量,但实际语义是“访问当前请求或当前应用对应的数据”。这就是 Flask 上下文的核心价值。
二、请求上下文保存什么
请求上下文和一次 HTTP 请求强相关,主要让这些对象可用:
from flask import request, session
request 保存请求方法、路径、参数、Header、Body、文件等信息;session 保存当前用户会话数据。它们只有在请求处理期间才有意义。请求结束后,上下文弹出,再访问它们就会报错。
例如下面代码在视图外直接执行会出问题:
print(request.path)
因为此时没有当前请求。面试时要强调:request 是当前请求上下文里的对象,不是随时可用的全局状态。
三、应用上下文保存什么
应用上下文和 Flask 应用相关,主要让这些对象可用:
from flask import current_app, g
current_app 指向当前应用对象,常用于读取配置、日志等:
current_app.config["DATABASE_URL"]
g 是一个临时存储对象,常用于一次请求或一次上下文期间共享数据:
from flask import g
def get_db():
if "db" not in g:
g.db = connect_db()
return g.db
g 不是跨请求缓存。请求结束后,相关上下文会清理,g 中的数据也不应该被当成持久状态。
四、应用上下文和请求上下文的关系
一次正常 HTTP 请求进来时,Flask 通常会同时推入请求上下文和应用上下文。请求上下文依赖应用上下文,因为处理请求必须知道当前属于哪个 app。
但应用上下文可以单独存在。比如在命令行脚本、测试、初始化任务中,你可能不处理 HTTP 请求,但需要读取 current_app.config 或初始化扩展,这时可以手动推入应用上下文:
with app.app_context():
init_database()
如果你在脚本里访问 current_app 报 “working outside of application context”,通常就是缺少 app.app_context()。
五、LocalProxy 和上下文局部的理解
Flask 的 request、current_app 等对象本质上是代理。代理对象会在访问属性时,到当前上下文中找到真实对象。这也是为什么你导入的是同一个 request 名字,但不同请求里读到的是不同数据。
早期 Flask 主要依赖线程/协程局部存储实现隔离,现代版本底层更多基于 Python 的 contextvars。面试不一定要讲到底层源码,但至少要说清楚:这些对象不是全局共享状态,而是按上下文隔离。
六、常见错误和工程场景
常见错误一:在后台线程或 Celery 任务里直接使用 request。后台任务已经脱离 HTTP 请求上下文,应该把需要的数据作为参数传进去,而不是依赖 request。
常见错误二:把 g 当成全局缓存。g 的生命周期跟上下文有关,不适合保存跨请求数据。跨请求缓存应该使用 Redis、内存缓存或数据库。
常见错误三:测试中忘记创建上下文。测试视图时可以用 app.test_client(),测试依赖 current_app 的函数时可以用 app.app_context()。
七、常见误区与追问
| 对象 | 所属上下文 | 生命周期 |
|---|---|---|
request | 请求上下文 | 单次 HTTP 请求 |
session | 请求上下文 | 依赖当前请求的 Cookie 会话 |
current_app | 应用上下文 | 当前 Flask app |
g | 应用上下文 | 一次应用上下文内的临时数据 |
- 误区:
request、current_app是普通全局对象。 它们是LocalProxy代理,访问时会到当前上下文中取真实对象。 - 误区:
g可以当跨请求缓存。g只适合一次请求或一次应用上下文内临时存放数据,跨请求缓存应使用 Redis、数据库或进程内缓存。 - 误区:后台任务里可以直接用 request。 后台任务脱离请求上下文,应把用户 ID、参数等必要数据显式传入。
- 追问:请求上下文和应用上下文谁依赖谁? 请求处理通常需要知道当前 app,所以请求上下文会伴随应用上下文;应用上下文也可以单独推入。
- 追问:为什么脚本里访问 current_app 会报错? 脚本没有自动请求入口,需要用
with app.app_context():手动推入应用上下文。 - 追问:现代 Flask 为什么更适合异步和协程场景? 现代实现更多依赖
contextvars隔离上下文,比单纯线程局部更适合协程并发模型。
记忆钩子:上下文让“看似全局”的对象按当前请求或当前应用隔离;报 outside context,先想有没有正确入栈。
八、加强记忆
Flask 上下文解决的是“看起来像全局变量,实际按当前请求或当前应用隔离”的问题。请求上下文让 request/session 可用,应用上下文让 current_app/g 可用;离开上下文访问这些对象会报错,后台任务和脚本要显式传参或手动推入上下文。