上下文是在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上下文机制的原理有了清晰的认识。