深刻 Flask 源碼理解 Context

Flask 中的上下文對象

知乎問題 編程中什麼是「Context(上下文)」 已經可以簡單地說明什麼是 Context,它是一個程序須要的外部對象,相似於一個全局變量。而這個變量的值會根據提供的值而改變。html

Flask 中有分爲請求上下文和應用上下文:python

對象 Context類型 說明
current_app AppContext 當前的應用對象
g AppContext 處理請求時用做臨時存儲的對象
request RequestContext 請求對象,封裝了Http請求的內容
session RequestContext 用於存儲請求之間須要記住的值

Flask 分發請求以前激活程序請求上下文,請求處理完成後再將其刪除。git

Flask 中的 Context 是經過棧來實現。github


Flask 的 Context 實現

Flask 的核心功能依賴於 Werkzeug 庫。數據庫

_app_ctx_stack & _request_ctx_stack

這兩種棧定義在 flask/global.py 中。編程

_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()

首先須要瞭解一下 Werkzeug 中關於 LcoalStack 的相關內容。flask

Local設計模式

Local 是定義了一個 __storage__ 字典,其中的鍵爲 threadid 值。api

class Local(object):
    __slots__ = ('__storage__', '__ident_func__')
    
    def __init__(self):
        object.__setattr__(self, '__storage__', {})
        object.__setattr__(self, '__ident_func__', get_ident)
        
    def __setattr__(self, name, value):
        ident  = self.__ident_func__()
        storage = self.__storage__
        try:
            storage[ident][name] = value
        except KeyError:
            raise AttributeError(name)
    ...

LocalStacksession

LocalStack 則內部維護一個 Local 實例。主要的做用是將 Local 維護的 __storage__ 字典中鍵爲 __ident_func__() 對應的值定義爲 {"stack" : [] }

class LocalStack(object):
    def __init__(self):
        self._local = Local()
        
    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, obj):
        pass

LocalProxy

LocalProxy類是一個代理類,應用到設計模式當中的代理模式。簡單地講,咱們不須要去了解當前的環境,而直接去操做這個 Proxy 類,這個 Proxy 類會將全部的操做反饋給正確的對象。

class LocalProxy(object):
    __slots__ = ('__local', '__dict__', '__name__')
    def __init__(self, local, name=None):
        object.__setattr__(self, '_LocalProxy__local', local)
        object.__setattr__(self, '__name__', name)
    
    def _get_current_object(self):
        # 經過此方法獲取被代理的對象
        if not hasattr(self.__local, '__release_local__')
            return self.__local
        try:
            return gerattr(self.__local,self.__name__)
        except Attribute:
            raise RuntimeError('no object bound to %s' % self.__name__)
    ...
    # 其餘操做

request & RequestContext

Flask 源碼中關於 request 的定義:

def _lookup_req_object(name):
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError(_request_ctx_err_msg)
    return getattr(top, name)
    
request = LocalProxy(partial(_lookup_req_object, 'request'))

從源碼能夠看出,request_request_ctx_stack 棧頂元素的一個屬性。實際上 _request_ctx_stack 棧中的元素是 ReuqestContext 對象的實例, 而 ReuqestContext 中包含了 request 請求的全部信息,包括 Session 信息。

class ReuqestContext(object):
    def __init__(self, app, environ, request=None):
        if reuqest is None:
            request  = Request(environ)
        self.requst = request
        self.app = app 
        self.session = None
        ...
        # 這個列表包含了與 request 相關聯的 Application
        self._implicit_app_ctx_stack = []
        self.match_request()

    def push(self, object):
        """
        這裏須要實現的是:當 RequestContext push 到
        _request_ctx_stack 時, 須要檢測是否有對應的
        AppContext。若是沒有,則會將當前 self.app push
        到 AppContext 中,同時將self.app 加入
        _implicit_app_ctx_stack 列表中; 不然
        _implicit_app_ctx_stack 添加 None。
        """
        pass
        
    def pop(self):
        """
        當 ReuqestContext 彈出 _request_ctx_stack 的
        方法。注意:request 清理以後的動做。如執行
        teardown_request。
        """
        pass

這裏傳入的 app,就是 Flask 的程序實例。
RequestContext 實例的建立在 Flask 類方法中。

class Flask(_PackageBoundObject):
    ...
    request_class = ReuqestContext
    def wsgi_app(self, environ, start_response):
        ctx = self.request_class(environ)
        ctx.push
        ...
        
    def __call__(self, environ, start_response):
        return self.wsgi_app(environ, start_response)

Flask 中 Request 對象繼承了 Werkzeug 中的 Request 對象。
上述代碼涉及到 WSGI,它強調 Appication 必須是一個可調用對象。
後期的工做之一是瞭解 WSGI

Session

在 session.py 文件中定義了 有關Session的內容。Flask 中 Session 是構建在 Cookie 上面的。其中定義了關於 Session 的接口。

class SessionMixin(object):
    """定義了Session的最小屬性"""
    
class SecureCookieSession(CallDict, SessionMixin):
    """ CallDict 是 werkzeug 中的數據結構 """

class NullSession(SecureCookieSession):
    """ 定義了空 session 結構 """
    
class SessionInterface(object):
    """ 定義了 Session接口的屬性,依賴於 app.config 
    中的信息。同時,規定了只要是繼承SessionInterface
    必須實現 open_session 和 save_session 方法
    """
class SecureCookieSessionInterface(SessionInterface):
    """ 
    主要是實現了 open_session 和 save_session 方法
    """

以下代碼則是 session 的應用。

# flask/app.py
class Flask(_PackageBoundObject):
    session_interface = SecureCookieSessionInterface()
    def open_session(self, request):
        return self.session_interface.open_session(self, request)
        
    def save_session(self, session, response)
        return self.session_interface.save_session(\
            self, session, response)
            
    def process_response(self, response):
        ctx = _request_ctx_stack.top
        ...
        if not self.session_interface.is_null_session(ctx.session):
            self.save_session(ctx.session, response)

#ReuqestContext
class ReuqestContext():
    def push(self, object):
        ...
        self.session = self.app.open_session(self.reuqest)
        if self.session is None:
            self.session = self.app.make_null_session()
        ...

sessionRequestContext 中屬性,因此代理說明以下:

session = LocalProxy(partial(_lookup_req_object,'session')

current_app & g

通常來說, 在 Flask Web 開發時, Flask的實例是延遲建立的。也就是說 AppContext尚未壓入 _app_ctx_stack 中,因此咱們在編寫代碼時,是沒法獲取完整的 Flask 實例的屬性。而當用戶訪問時,程序的實例已經初始化完成了,所以咱們採用 current_app代理獲取當前 app。這僅僅是個人我的理解。實際上這是解決 多個 Flask 實例運行的問題

current_app是獲取 _app_ctx_stack 棧頂 AppContext實例元素的代理.

def _find_app():
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError(_app_ctx_err_msg)
    return top.app
current_app = LocalProxy(_find_app)

flask.g 是存儲一下資源信息的,如數據庫鏈接信息。更多應用的則是體如今 Flask 擴展當中。

def _lookup_app_object(name):
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError(_app_ctx_err_msg)
        return getattr(top,name)
g = LocalProxy(partical(_lookup_app_object, 'g'))

# flask.app.py
class Flask(_PackageBoundObject):
    app_ctx_globals_class = _AppCtxGlobals #實現的是相似字典的功能

# AppContext
class AppContext(object):
    def __init__(self, app):
        self.g = self.app.app_ctx_globals_class()

#RequestContext
class RequestContext(object):
    #定義與request相關的 g 變量
    def _get_g(self):
        return _app_ctx_stack.top.g
    def _set_g(self, value):
        _app_ctx_stack.top.g = value
    g = property(_get_g, _set_g)
    del _get_g, _set_g

上述代碼存在一個疑問是 g 對象是基於請求的,每次請求都會重置。那麼 g 爲何不是 RequestContext 而是 AppContext ?
flask.g API 文檔 中說明了 g 變量的改動。


我的博客

相關文章
相關標籤/搜索