上下文是在Flask開發中的一個核心概念,本文將經過閱讀源碼分享下其原理和實現。html
Flask系列文章:python
首先,什麼是Flask中的上下文?git
在Flask中,對一個請求進行處理時,視圖函數通常都會須要請求參數、配置等對象,固然不能對每一個請求都傳參一層層到視圖函數(這顯然很不優雅嘛),爲此,設計出了上下文機制(好比像咱們常常會調用的request就是上下文變量)。github
Flask中提供了兩種上下文:flask
這四個是上下文變量具體的做用是什麼?設計模式
具體是怎麼實現的呢?cookie
上下文具體的實現文件:ctx.pysession
請求上下文對象經過RequestContext類實現,當Flask程序收到請求時,會在wsgi_app()中調用Flask.request_context(),實例化RequestContext()做爲請求上下文對象,接着會經過push()方法將請求數據推入到請求上下文堆棧(LocalStack),而後經過full_dispatch_request對象執行視圖函數,調用完成以後經過auto_pop方法來移除。因此,請求上下文的生命週期開始於調用wsgi_app()時,結束與響應生成以後。具體代碼:app
def wsgi_app(self, environ, start_response): ctx = self.request_context(environ) error = None try: try: ctx.push() response = self.full_dispatch_request() except Exception as e: error = e response = self.handle_exception(e) except: # noqa: B001 error = sys.exc_info()[1] raise return response(environ, start_response) finally: if self.should_ignore_error(error): error = None ctx.auto_pop(error)
程序上下文對象經過AppContext類實現,程序上下文的建立方式有兩種:ide
經過閱讀源碼,能夠看到上面兩個上下文對象的push和pop都是經過操做LocalStack對象實現的,那麼,LocalStack是怎樣實現的呢?
Werkzeug的LocalStack是棧結構,在 globals.py中定義:
_request_ctx_stack = LocalStack() _app_ctx_stack = LocalStack()
具體的實現:
class LocalStack(object): def __init__(self): self._local = Local() def __release_local__(self): self._local.__release_local__() def _get__ident_func__(self): return self._local.__ident_func__ def _set__ident_func__(self, value): object.__setattr__(self._local, '__ident_func__', value) __ident_func__ = property(_get__ident_func__, _set__ident_func__) del _get__ident_func__, _set__ident_func__ def __call__(self): def _lookup(): rv = self.top if rv is None: raise RuntimeError('object unbound') return rv return LocalProxy(_lookup) def push(self, obj): """Pushes a new item to the stack""" rv = getattr(self._local, 'stack', None) if rv is None: self._local.stack = rv = [] rv.append(obj) return rv def pop(self): """Removes the topmost item from the stack, will return the old value or `None` if the stack was already empty. """ stack = getattr(self._local, 'stack', None) if stack is None: return None elif len(stack) == 1: release_local(self._local) return stack[-1] else: return stack.pop() @property def top(self): """The topmost item on the stack. If the stack is empty, `None` is returned. """ try: return self._local.stack[-1] except (AttributeError, IndexError): return None
能夠看到:
__call__
方法,當實例被調用直接返回棧頂對象的Werkzeug提供的LocalProxy代理,即LocalProxy實例,因此,_request_ctx_stack
和_app_ctx_stack
都是代理。看到這裏,就有如下問題:
Local類是怎樣存儲數據的呢?爲啥須要存儲到Local中?
先看下代碼:
try: from greenlet import getcurrent as get_ident except ImportError: try: from thread import get_ident except ImportError: from _thread import get_ident class Local(object): __slots__ = ("__storage__", "__ident_func__") def __init__(self): object.__setattr__(self, "__storage__", {}) object.__setattr__(self, "__ident_func__", get_ident) def __iter__(self): return iter(self.__storage__.items()) def __call__(self, proxy): """Create a proxy for a name.""" return LocalProxy(self, proxy) def __release_local__(self): self.__storage__.pop(self.__ident_func__(), None) def __getattr__(self, name): try: return self.__storage__[self.__ident_func__()][name] except KeyError: raise AttributeError(name) def __setattr__(self, name, value): ident = self.__ident_func__() storage = self.__storage__ try: storage[ident][name] = value except KeyError: storage[ident] = {name: value} def __delattr__(self, name): try: del self.__storage__[self.__ident_func__()][name] except KeyError: raise AttributeError(name)
能夠看到,Local構造函數中定義了兩個屬性:
__storage__
:用來保存每一個線程的真實數據,對應的存儲結構爲->{線程ID:{name:value}}
__ident_func__
:經過get_ident()方法獲取線程ID,能夠看到優先會使用Greenlet獲取協程ID,其次是thread模塊的線程IDLocal類在保存數據的同時,記錄對應的線程ID,獲取數據時根據當前線程的id便可獲取到對應數據,這樣就保證了全局使用的上下文對象不會在多個線程中產生混亂,保證了每一個線程中上下文對象的獨立和準確。
能夠看到,Local類實例被調用時也一樣的被包裝成了一個LocalProxy代理,爲何要用LocalProxy代理?
代理是一種設計模式,經過建立一個代理對象來操做實際對象,簡單理解就是使用一箇中間人來轉發操做,Flask上下文處理爲何須要它?
看下代碼實現:
@implements_bool class LocalProxy(object): __slots__ = ('__local', '__dict__', '__name__', '__wrapped__') def __init__(self, local, name=None): object.__setattr__(self, '_LocalProxy__local', local) object.__setattr__(self, '__name__', name) if callable(local) and not hasattr(local, '__release_local__'): object.__setattr__(self, '__wrapped__', local) def _get_current_object(self): """ 獲取被代理的實際對象 """ if not hasattr(self.__local, '__release_local__'): return self.__local() try: return getattr(self.__local, self.__name__) except AttributeError: raise RuntimeError('no object bound to %s' % self.__name__) @property def __dict__(self): try: return self._get_current_object().__dict__ except RuntimeError: raise AttributeError('__dict__') def __repr__(self): try: obj = self._get_current_object() except RuntimeError: return '<%s unbound>' % self.__class__.__name__ return repr(obj) def __bool__(self): try: return bool(self._get_current_object()) except RuntimeError: return False def __unicode__(self): try: return unicode(self._get_current_object()) # noqa except RuntimeError: return repr(self) def __dir__(self): try: return dir(self._get_current_object()) except RuntimeError: return [] 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] ...
經過__getattr__()
、__setitem__()
和__delitem__
會動態的更新實例對象。
再結合上下文對象的調用:
current_app = LocalProxy(_find_app) request = LocalProxy(partial(_lookup_req_object, "request")) session = LocalProxy(partial(_lookup_req_object, "session")) g = LocalProxy(partial(_lookup_app_object, "g"))
咱們能夠很明確的看到:由於上下文的推送和刪除是動態進行的,因此使用代理來動態的獲取上下文對象。
以上,但願你對Flask上下文機制的原理有了清晰的認識。