flask-0.1 源碼中有五個全局對象, 分別是: _request_ctx_stack
, current_app
, request
, session
和 g
. 從源碼能夠看出, 其他四個都會依賴於 _request_ctx_stack
. 單從命名上來看, 這應該是一個用於存放請求上下文的棧.python
# 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)
複製代碼
這就讓我很是不解了.git
首先回答我不解的是 Flask 中的 url_for
和 redrict
. Flask 是支持在內部直接重定向的, 用棧來存放請求上下文能夠很方便地支持這一特性, 可是若是僅僅是用一個請求上下文實例而不是棧的話倒是很難實現的. 舉個🌰:
若是 A 請求過來, 你須要在處理 A 請求的時候重定向到 B, 這個時候, 若是是棧的話, 當前請求就會被"掛起", B 請求會被壓入棧中, 等待 B 請求處理完, 從棧中 pop 出來以後, 就能夠繼續處理 A 請求, 或者將 B 請求的結果做爲 A 請求的結果返回; 可是若是隻是一個實例的話, A 請求上下文就會被 B 替換, 就會形成 A 請求沒法返回.github
要搞清楚這是一個什麼棧, 就得引入多線程了. 若是咱們以多個線程啓動 Flask, 那即可以同時處理多個請求, 可是 Flask 實例只有一個, 那它又是如何處理請求上下文的呢? 它是怎麼作到不一樣線程上請求上下文分離的呢?flask
仔細觀察上面的源碼部分, 能夠發現 Flask 的全局對象 _request_ctx_stack
是 Werkzeug LocalStack
類的一個實例. 可見, 不一樣線程請求上下文分離的奧祕應該隱藏在 LocalStack
這個類中.
爲了理解 LocalStack
,咱們先引入 Werkzeug 的另一個類 --- Local
.
簡單來講, Local
實現的功能是同一個實例在不一樣線程(Werkzeug支持線程和 greenlet)下變量的分離. 好比下面的例子中, Local
的實例是全局的, 可是在不一樣的線程(greenlet)下, 實例中變量的值能夠是不一樣的. 當你在線程 1 中訪問 local.name 的時候, 返回的值是 John, 可是在線程 2 中返回的倒是 'Debbie':session
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, 從而作到了不一樣線程下上下文變量的分離.app
理解 Local
後, 繼續看 Flask 中用來實現請求棧的 LocalStack
. 先看看看簡化版的 LocalStack
實現:ide
""" 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
方法, 分別對應進棧, 出棧和獲取棧頂元素三中常見棧操做.this
另外, 其實 Flask 的這幾個全局變量仍是和 Local
, LocalStack
不太同樣, 它是用 LocalProxy
實現的. 那 LocalProxy
又是什麼?
講真, 這個困惑了我好久.
首先仍是解釋一下 LocalProxy
的做用吧. 就如它命名所示, 它起到的就是一個代理的做用.
就拿上面文中的 request
舉例來講, LocalProxy
的做用就是把全部對 request
的操做所有代理到請求棧頂元素(_request_ctx_stack.top.request
)中.url
""" 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 源碼解析:上下文