Flask0.1源碼(1)-五個全局對象

flask-0.1 源碼中有五個全局對象, 分別是: _request_ctx_stack, current_app, request, sessiong. 從源碼能夠看出, 其他四個都會依賴於 _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 同一時間只能處理一個請求, 那麼只須要一個請求上下文實例也能夠達到一樣的效果, 爲何會用棧來實現呢?
Stack Overflow 上有一個小哥和我同樣不解, 問題下面的回答很是精彩(建議回答和評論都仔細看一下).

首先回答我不解的是 Flask 中的 url_forredrict. 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
複製代碼

LocalStackLocal 包了一層(在 Local 中保存了一個棧), 因此依然支持不一樣線程變量分離. 每一個線程中都維護了一個請求棧, 且各個棧互不干擾. 而且實現了 push, poptop 方法, 分別對應進棧, 出棧和獲取棧頂元素三中常見棧操做.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 源碼解析:上下文

相關文章
相關標籤/搜索