一、引言:为什么 Flask 需要“上下文”?
在 Web 开发中,我们经常需要访问当前请求的信息(如 URL、表单数据)、当前应用实例(如配置、数据库连接)或用户会话状态。
传统做法是使用全局变量:
# ❌ 危险!线程不安全
request = None
def handle_request(environ):
global request
request = parse_request(environ)
return view_function() # 此时 request 可能被其他请求覆盖!
但在多线程或多协程服务器(如 Gunicorn、Uvicorn)中,多个请求并发执行。如果所有线程共享同一个 request 变量,就会出现数据错乱——A 请求读到了 B 请求的数据!
🔍 问题本质:并发环境下的“状态隔离”
我们需要一种机制,让每个请求都拥有自己的“沙箱”,在这个沙箱里可以安全地访问“当前请求”、“当前应用”等信息,而不会与其他请求冲突。
这就是 上下文(Context)机制 的由来。
二、Flask 的解决方案:上下文栈(Context Stack)
Flask 借助 Werkzeug 提供的 LocalStack 和 LocalProxy,实现了线程/协程级别的隔离。
2.1 核心组件:LocalStack 与 LocalProxy
| 组件 |
作用 |
|
|
每个线程/协程独享的栈结构,用于存放上下文对象 |
|
|
代理对象,动态指向当前栈顶的上下文属性 |
# werkzeug/local.py 简化实现
class LocalStack:
def __init__(self):
self._local = Local() # threading.local 或 contextvars.ContextVar
def push(self, obj):
rv = getattr(self._local, 'stack', None)
if rv is None:
self._local.stack = rv = []
rv.append(obj)
return rv
def pop(self):
stack = getattr(self._local, 'stack', None)
if stack is None or len(stack) == 0:
return None
return stack.pop()
@property
def top(self):
try:
return self._local.stack[-1]
except (AttributeError, IndexError):
return None
💡 Local() 在 Python < 3.7 使用 threading.local,Python ≥ 3.7 使用 contextvars 实现真正的协程安全。
2.2 上下文代理对象是如何工作的?
from werkzeug.local import LocalProxy
# 内部定义
_app_ctx_stack = LocalStack()
_req_ctx_stack = LocalStack()
# 创建代理对象
current_app = LocalProxy(lambda: _app_ctx_stack.top.app)
request = LocalProxy(lambda: _req_ctx_stack.top.request)
g = LocalProxy(lambda: _app_ctx_stack.top.g)
session = LocalProxy(lambda: _req_ctx_stack.top.session)
LocalProxy接收一个可调用对象(通常是 lambda)。- 每次访问
current_app.name时,LocalProxy自动调用该 lambda,从当前线程的栈中查找最新上下文。 - 因此,它不是“存储值”,而是“动态查找值”。
✅ 优势:看似是全局变量,实则是线程/协程局部变量,完美解决并发安全问题。
三、两种上下文详解:AppContext 与 RequestContext
Flask 定义了两种上下文对象:
| 上下文类型 |
对应类 |
生命周期 |
主要用途 |
依赖关系 |
| 应用上下文(Application Context) |
|
通常与请求一致,也可独立存在 < |

最低0.47元/天 解锁文章
1356

被折叠的 条评论
为什么被折叠?



