Flask源码解析:Flask上下文
一、上下文(Context)
什么是上下文:
每一段程序都有很多外部变量。只有像Add这种简单的函数才是没有外部变量的。一旦你的一段程序有了外部变量,这段程序就不完整,不能独立运行。你为了使他们运行,就要给所有的外部变量一个一个写一些值进去。这些值的集合就叫上下文。
譬如说在C++的lambda表达是里面,[写在这里的就是上下文](int a, int b){ ... }。--- 引用 vczh
当接受到一个请求时,我们通常将请求封装成一个HttpRequest对象,然后将对象传递给每一个视图函数,我们从request对象中获取相关的请求信息,这样做的缺点就是所有的视图函数都需要添加reqeust参数,即使视图函数中并没有使用到它。
而在Flask中,将这些信息封装成类似全局变量的东西,视图函数需要的时候,可以使用from flask import request获取。但是这些对象和全局变量不同的是:他们必须是动态的,也就是说在多线程/多协程的情况下,每个线程获取的request都是属于自己的,不会相互干扰。
Flask提供了应用上下文(Application Context)和请求上下文(Reqeust Context).
上下文的有关定义在:flask/globals.py中
def _lookup_req_object(name): top = _request_ctx_stack.top if top is None: raise RuntimeError(_request_ctx_err_msg) return getattr(top, name) def _lookup_app_object(name): top = _app_ctx_stack.top if top is None: raise RuntimeError(_app_ctx_err_msg) return getattr(top, name) def _find_app(): top = _app_ctx_stack.top if top is None: raise RuntimeError(_app_ctx_err_msg) return top.app # context locals _request_ctx_stack = LocalStack() _app_ctx_stack = LocalStack() 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提供了两种上下文:request context 和 application context 。request context有衍生出request和session,appliction context衍生出current_app 和 g.
请求上下文和应用上下文是通过LocalStack()和LocalProxy()来实现的
LocalStack()和LocalProxy()是由Werkzeug提供,定义在werkzeug/local.py中,在分析这两个类之前,我们需要先分析另一个类Local。
二、Local类
Local类位于werkzeug/local.py中,它为LocalStack和LocalProxy提供了基础,Local就是实现了threading.local的效果——在多线程情况下全局变量的隔离效果。
class Local(object): __slots__ = ('__storage__', '__ident_func__') def __init__(self): # 用于数据的保存 object.__setattr__(self, '__storage__', {}) # 用于获取当前线程的id 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: # 通过线程id获取数据 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: # 通过线程id绑定数据 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)
存储形式键为线程/协程标识数值,值为字典类型的数据。
三、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中 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
通过Local和LocalStack,我们可以了解到数据是以字典的形式存储在LocalStack中的,键为线程/协程id,值也是字典类型。LocalStack
还实现了push
、pop
、top
等方法。其中top
方法永远指向栈顶的元素。栈顶的元素是指当前线程/协程中最后被推入栈中的元素。
我们在flask/globals.py中看到请求上下文的定义,他就是一个LocalStack的实例:
_request_ctx_stack = LocalStack()
至于为什么要用到栈的结构,而不是直接使用Local()呢?
四、LocalProxy类
LocalProxy类是一个Local对象的代理,负责把所有对自己的操作都转发个内部的Local对象
class LocalProxy(object): """Acts as a proxy for a werkzeug local. Forwards all operations to a proxied 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): """Return the current object.""" 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 __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
这里实现的关键是把通过参数传递进来的 Local
实例保存在 __local
属性中,并定义了 _get_current_object()
方法获取当前线程或者协程对应的对象。
继续回到request context的实现:
_request_ctx_stack = LocalStack()
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))
_request_ctx_stack
是多线程或者协程隔离的栈结构
request
每次都会调用 _lookup_req_object
栈头部的数据来获取保存在里面的 requst context。