知乎問題 程式設計中什麼是「Context(上下文)」 已經能夠簡單地說明什麼是 Context,它是一個程式需要的外部物件,類似於一個全域性變數。而這個變數的值會根據提供的值而改變。
Flask 中有分為請求上下文和應用上下文:
物件 | Context型別 | 說明 |
---|---|---|
current_app | AppContext | 當前的應用物件 |
g | AppContext | 處理請求時用作臨時儲存的物件 |
request | RequestContext | 請求物件,封裝了Http請求的內容 |
session | RequestContext | 用於儲存請求之間需要記住的值 |
Flask 分發請求之前啟用程式請求上下文,請求處理完成後再將其刪除。
Flask 中的 Context 是通過棧來實現。
Flask 的 Context 實現
Flask 的核心功能依賴於 Werkzeug 庫。
_app_ctx_stack & _request_ctx_stack
這兩種棧定義在 flask/global.py
中。
1 2 |
_request_ctx_stack = LocalStack() _app_ctx_stack = LocalStack() |
首先需要了解一下 Werkzeug 中關於 LcoalStack
的相關內容。
Local
類
Local
是定義了一個 __storage__
字典,其中的鍵為 thread
的 id
值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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) ... |
LocalStack
類
LocalStack
則內部維護一個 Local
例項。主要的作用是將 Local
維護的 __storage__
字典中鍵為 __ident_func__()
對應的值定義為 {"stack" : [] }
。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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
類會將所有的操作反饋給正確的物件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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
的定義:
1 2 3 4 5 6 7 |
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
資訊。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
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
類方法中。
1 2 3 4 5 6 7 8 9 10 |
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
的介面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
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
的應用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# 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() ... |
session
是 RequestContext
中屬性,所以代理說明如下:
1 |
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
例項元素的代理.
1 2 3 4 5 6 |
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 擴充套件當中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
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
變數的改動。