flask-0.1 源码中有五个全局对象, 分别是: _request_ctx_stack, current_app, request, session 和 g. 从源码可以看出, 其余四个都会依赖于 _request_ctx_stack. 单从命名上来看, 这应该是一个用于存放请求上下文的栈.
# flask-0.1 中不区分 app_ctx 和 request_ctx
# context locals
_request_ctx_stack = LocalStack()
current_app = LocalProxy(lambda: _request_ctx_stack.top.app)
request = LocalProxy(lambda: _request_ctx_stack.top.request)
session = LocalProxy(lambda: _request_ctx_stack.top.session)
g = LocalProxy(lambda: _request_ctx_stack.top.g)
这就让我非常不解了.

我们先抛开具体栈的实现, 仅仅当它是一个栈来考虑, 并且忽略多线程. 只有一个线程的情况下, Flask 同一时间只能处理一个请求, 那么只需要一个请求上下文实例也可以达到同样的效果, 为什么会用栈来实现呢?
Stack Overflow 上有一个小哥和我一样不解, 问题下面的回答非常精彩(建议回答和评论都仔细看一下).
首先回答我不解的是 Flask 中的 url_for 和 redrict. Flask 是支持在内部直接重定向的, 用栈来存放请求上下文可以很方便地支持这一特性, 但是如果仅仅是用一个请求上下文实例而不是栈的话却是很难实现的. 举个🌰:
如果 A 请求过来, 你需要在处理 A 请求的时候重定向到 B, 这个时候, 如果是栈的话, 当前请求就会被"挂起", B 请求会被压入栈中, 等待 B 请求处理完, 从栈中 pop 出来之后, 就可以继续处理 A 请求, 或者将 B 请求的结果作为 A 请求的结果返回; 但是如果只是一个实例的话, A 请求上下文就会被 B 替换, 就会造成 A 请求无法返回.
搞清楚为什么用栈来储存请求上下文之后, 我们再来栈的具体实现.
要搞清楚这是一个什么栈, 就得引入多线程了. 如果我们以多个线程启动 Flask, 那便可以同时处理多个请求, 但是 Flask 实例只有一个, 那它又是如何处理请求上下文的呢? 它是怎么做到不同线程上请求上下文分离的呢?
仔细观察上面的源码部分, 可以发现 Flask 的全局对象 _request_ctx_stack 是 Werkzeug LocalStack 类的一个实例. 可见, 不同线程请求上下文分离的奥秘应该隐藏在 LocalStack 这个类中.
为了理解 LocalStack,我们先引入 Werkzeug 的另外一个类 --- Local.
简单来说, Local 实现的功能是同一个实例在不同线程(Werkzeug支持线程和 greenlet)下变量的分离. 比如下面的例子中, Local 的实例是全局的, 但是在不同的线程(greenlet)下, 实例中变量的值可以是不同的. 当你在线程 1 中访问 local.name 的时候, 返回的值是 John, 但是在线程 2 中返回的却是 'Debbie':
local = Local()
# ...
# on thread 1
local.name = 'John'
# ...
# on thread 2
local.name = 'Debbie'
那这是怎么做到的呢? 简化版的 Local 实现如下:
"""
Local 源码:
https://github.com/pallets/werkzeug/blob/5d80fa2cd1008abaa9450b40a44f0df90a842489/werkzeug/local.py#L51
"""
# 简化版 Local 实现
class Local:
def __init__(self)
self.storage = {}
def __getattr__(self, name):
context_id = get_ident() # we get the current thread's or greenlet's id
contextual_storage = self.storage.setdefault(context_id, {})
try:
return contextual_storage[name]
except KeyError:
raise AttributeError('name')
def __setattr__(self, name, value):
context_id = get_ident()
contextual_storage = self.storage.setdefault(context_id, {})
contextual_storage[name] = value
def __release_local__(self):
context_id = get_ident()
self.storage.pop(context_id, None)
local = Local()
代码中 get_ident() 起着至关重要的作用, 它能够识别出当前所在的线程, 并且将当前所在线程的上下文变量储存在实例字典中, 以线程 id 作为 key, 从而做到了不同线程下上下文变量的分离.
理解 Local 后, 继续看 Flask 中用来实现请求栈的 LocalStack.
先看看看简化版的 LocalStack 实现:
"""
LocalStack 源码:
https://github.com/pallets/werkzeug/blob/5d80fa2cd1008abaa9450b40a44f0df90a842489/werkzeug/local.py#L89
"""
# 简化版 LocalStack
class LocalStack:
def __init__(self):
self.local = Local()
def push(self, obj):
"""
将新元素 push 到栈中
"""
stack = getattr(self.local, 'stack', None)
if stack is None:
stack = []
self.local.stack = stack
stack.append(obj)
return stack
def pop(self):
"""
pop 出栈顶元素
如果栈为空, 返回 None
"""
stack = getattr(self.local, 'stack', None)
if stack is None:
return None
elif len(stack) = 1:
release_local(self.local) # this simply releases the local
return stack[-1]
else:
return tack.pop()
@property
def top(self):
"""
获取站顶元素, 不出栈
若栈为空, 同样返回 None
"""
try:
return self.local.stack[-1]
except (AttributeError, IndexError):
return None
LocalStack 将 Local 包了一层(在 Local 中保存了一个栈), 所以依然支持不同线程变量分离. 每个线程中都维护了一个请求栈, 且各个栈互不干扰. 并且实现了 push, pop 和 top 方法, 分别对应进栈, 出栈和获取栈顶元素三中常见栈操作.
另外, 其实 Flask 的这几个全局变量还是和 Local, LocalStack 不太一样, 它是用 LocalProxy 实现的. 那 LocalProxy 又是什么?
讲真, 这个困惑了我很久.
首先还是解释一下 LocalProxy 的作用吧. 就如它命名所示, 它起到的就是一个代理的作用.
就拿上面文中的 request 举例来说, LocalProxy 的作用就是把所有对 request 的操作全部代理到请求栈顶元素(_request_ctx_stack.top.request)中.
"""
LocalProxy 源码:
https://github.com/pallets/werkzeug/blob/5d80fa2cd1008abaa9450b40a44f0df90a842489/werkzeug/local.py#L254
"""
# 简化版 LocalProxy
class LocalProxy(object):
def __init__(self, local, name):
# local 是一个 Local 实例
# 或者是可回调的, 通过它可以获取到被代理的对象
self.local = local
# `name` 是被代理的对象的名字(key)
self.name = name
def _get_current_object(self):
# 如果 local 是一个 Local 实例, 则他会有 `__release_local__` (被用于释放 local 对象)
if hasattr(self.local, '__release_local__'):
try:
return getattr(self.local, self.name)
except AttributeError:
raise RuntimeError('no object bound to %s' % self.name)
# 如果不是 Local 实例, 就必须可以通过直接调用它获取到被代理的对象
return self.local(self.name)
# 以下所有的魔术方法都被重写
# 使得对 LocalProxy 实例的所有操作都会被代理到被代理的对象
@property
def __dict__(self):
try:
return self._get_current_object().__dict__
except RuntimeError:
raise AttributeError('__dict__')
def __repr__(self):
try:
return repr(self._get_current_object())
except RuntimeError:
return '<%s unbound>' % self.__class__.__name__
def __bool__(self):
try:
return bool(self._get_current_object())
except RuntimeError:
return False
# ... etc etc ...
def __getattr__(self, name):
if name == '__members__':
return dir(self._get_current_object())
return getattr(self._get_current_object(), name)
def __setitem__(self, key, value):
self._get_current_object()[key] = value
def __delitem__(self, key):
del self._get_current_object()[key]
# ... and so on ...
__setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
__delattr__ = lambda x, n: delattr(x._get_current_object(), n)
__str__ = lambda x: str(x._get_current_object())
__lt__ = lambda x, o: x._get_current_object() < o
__le__ = lambda x, o: x._get_current_object() <= o
__eq__ = lambda x, o: x._get_current_object() == o
# ... and so forth ...
LocalProxy 重写了很多魔术方法, 使得所有对其实例的操作全部转移到被代理的对象上.
参考:
What is the purpose of Flask's context stacks?
werkzeug.local
flask 源码解析:上下文