在Flask源码解析(二):Flask的工作流程中说过,关于上下文的内容会专门写一篇文章来讲,今天就把这个内容写一写。再次声明,本文涉及到的Flask源码都是出自Flask0.1的源码,部分代码为了方便理解只保留核心部分。
首先,在了解上下文之前,必须要弄清楚Local、LocalStack、LocalProxy这三个概念。
Local
Python中的thread locals实现了线程隔离的数据访问方式,但是在web应用中存在以下问题:
- 多个greenlet协程可以在同一个线程中,所以无法保证协程之间数据的隔离
- 多个http请求会复用线程,之前请求的数据会残留在thread locals中
WerkZeug的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__字典实现greentlet或thread之间的数据隔离,这个字典的key是greenlet id或thread id(有greenlet模块时是greent id)
- 通过重写__getattr__和__setattr__方法,当我们使用Local对象访问属性时,会自动获取当前greentlet或thread存储空间里的属性值
- 通过__release_local__方法可以释放当前greentlet或thread的数据
LocalStack
LocalStack是基于Local实现的栈,提供了栈的push,pop,top方法。
LocalStack的实现
class LocalStack(object):
def __init__(self):
self._local = Local()
def __release_local__(self):
self._local.__release_local__()
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
- LocalStack内部维护的是_local对象的stack属性,所以它在greentlet或thread之间是数据隔离的
- 当栈中元素为空时,会自动调用release_local方法释放当前greentlet或thread的数据
LocalStack的用法
>>> ls = LocalStack()
>>> ls.push(42)
>>> ls.top
42
>>> ls.push(23)
>>> ls.top
23
>>> ls.pop()
23
>>> ls.top
42
LocalProxy
LocalProxy用于代理Local对象和LocalStack对象
LocalProxy的实现
@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):
"""Return the current object. This is useful if you want the real
object behind the proxy at a time for performance reasons or because
you want to pass the object into a different context.
"""
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())
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]
......
- LocalProxy重载了所有的基本方法,使得对LocalProxy对象执行的任何操作,都是通过是_get_current_object来获取被代理对象,然后执行相应的操作,从而实现了动态更新被代理对象的效果
LocalProxy的用法
使用Local、LocalStack对象的__call__ 方法
from werkzeug.local import Local
l = Local()
user = l("user")
from werkzeug.local import LocalStack
_request_stack = LocalStack()
request = _request_stack()
使用Local对象作为参数
l = Local()
user = LocalProxy(l, 'user')
使用callable对象作为参数
session = LocalProxy(lambda: request.session)
了解完Local、LocalStack、LocalProxy,接下来看一下上下文。
什么是上下文
程序中的上下文代表了程序当下所运行的环境,存储了一些程序运行的信息。
Flask中的上下文
在Flask0.1中只有请求上下文,请求上下文中的对象有:
- request:封装了HTTP请求的内容,针对的是http请求。比如request.args.get(‘user’)可以获取get请求的参数。
- session:用来记录请求会话中的信息,针对的是用户信息。比如session[‘name’] = user.id可以记录用户信息。
- current_app:表示当前运行程序文件的程序实例,比如可以通过current_app.name打印出当前应用程序实例的名字。
- g:用于临时存储的对象,每次请求都会重置这个变量。比如可以通过它传递一些临时数据。
特别说明:在Flask0.9增加了应用上下文并将current_app移动到应用上下文中,在Flask1.0中g对象也被移动到应用上下文中。
Flask中上下文的定义
_request_ctx_stack = LocalStack()
current_app = LocalProxy(lambda: _request_ctx_stack.top.app)
request = LocalProxy(lambda: _request_ctx_stack.top.request)
session = LocalProxy(lambda: _request_ctx_stack.top.session)
g = LocalProxy(lambda: _request_ctx_stack.top.g)
上下文处理流程
在上一篇文章2. Flask源码解析(二):Flask的工作流程.note中说到,wsgi_app方法中会创建上下文:
def wsgi_app(self, environ, start_response):
with self.request_context(environ):
rv = self.preprocess_request()
if rv is None:
rv = self.dispatch_request()
response = self.make_response(rv)
response = self.process_response(response)
return response(environ, start_response)
request_context()方法中会创建一个_RequestContext对象,_RequestContext源码如下:
class _RequestContext(object):
def __init__(self, app, environ):
self.app = app
self.url_adapter = app.url_map.bind_to_environ(environ)
self.request = app.request_class(environ)
self.session = app.open_session(self.request)
self.g = _RequestGlobals()
self.flashes = None
def __enter__(self):
_request_ctx_stack.push(self)
def __exit__(self, exc_type, exc_value, tb):
if tb is None or not self.app.debug:
_request_ctx_stack.pop()
从上面的代码可以看出,with self.request_context(environ)的作用就是对每个http请求,先创建请求上下文对象,放到_request_ctx_stack栈对象中,在响应返回之后把请求上下文对象弹出,这样在视图函数处理期间就可以从栈上获取request、session、current_app、g这几个对象
疑问解答
1.在Web应用运行时一个线程同时只处理一个请求,那么 _req_ctx_stack和 _app_ctx_stack肯定都是只有一个栈顶元素的。那么为什么还要用栈这种结构?
答:Flask的设计理念之一就是多应用的支持。当在一个应用的上下文环境中,需要嵌套处理另一个应用的相关操作时,这时就能体现出栈的好处了,只要通过调用_request_ctx_stack.top.app或者current_app可以获得当前上下文环境正在处理哪个应用。
|