在咱们使用Flask以及Werkzeug框架的过程当中,常常会遇到以下三个概念:Local、LocalStack和LocalProxy。尤为在学习Flask的Request Context和App Context的过程当中,这几个概念出现的更加频繁,另外不少Flask插件都会使用这三个概念对应的技术。那么这三个东西究竟是什么?咱们为何须要它们?以及如何使用呢?本篇文章主要就是来解答这些问题。python
这部分咱们重点介绍Local概念,主要分为如下几个部分:git
在Python的标准库中提供了thread local
对象用于存储thread-safe和thread-specific的数据,经过这种方式存储的数据只在本线程中有效,而对于其它线程则不可见。正是基于这样的特性,咱们能够把针对线程全局的数据存储进thread local
对象,举个简单的例子github
>>from threading import local >>thread_local_data = local() >>thread_local_data.user_name="Jim" >>thread_local_data.user_name 'Jim'
使用thread local
对象虽然能够基于线程存储全局变量,可是在Web应用中可能会存在以下问题:flask
thread local
中的数据多是以前残留的数据。为了解决上述问题,Werkzeug开发了本身的local对象,这也是为何咱们须要Werkzeug的local对象安全
先举一个简单的示例:ruby
from werkzeug.local import Local, LocalManager local = Local() local_manager = LocalManager([local]) def application(environ, start_response): local.request = request = Request(environ) ... # make_middleware会确保当request结束时,全部存储于local中的对象的reference被清除 application = local_manager.make_middleware(application)
local_manager.locals.append()
来添加。而当LocalManager对象清理的时候会将全部存储于locals中的当前context的数据都清理掉那么Werkzeug的Local对象是如何实现这种在相同的context环境下保证数据的全局性和隔离性的呢?bash
咱们先来看下源代码数据结构
# 在有greenlet的状况下,get_indent实际获取的是greenlet的id,而没有greenlet的状况下获取的是thread id 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()) # 当调用Local对象时,返回对应的LocalProxy def __call__(self, proxy): """Create a proxy for a name.""" return LocalProxy(self, proxy) # Local类中特有的method,用于清空greenlet id或线程id对应的dict数据 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)
__storage__
dict的封装,而这个dict中的key使用的就是get_indent函数获取的id(当有greenlet时使用greenlet id,没有则使用thread id)__storage__
dict中的value也是一个dict,这个dict就是该greenlet(或者线程)对应的local存储空间__getattr__
, __setattr__
等魔术方法,咱们在greenlet或者线程中使用local对象时,实际会自动获取greenlet id(或者线程id),从而获取到对应的dict存储空间,再经过name key就能够获取到真正的存储的对象。这个技巧实际上在编写线程安全或协程安全的代码时是很是有用的,即经过线程id(或协程id)来分别存储数据。>>> loc = Local() >>> loc.foo = 42 >>> release_local(loc) # release_local实际调用local对象的__release_local__ method >>> hasattr(loc, 'foo') False
LocalStack与Local对象相似,都是能够基于Greenlet协程或者线程进行全局存储的存储空间(实际LocalStack是对Local进行了二次封装),区别在于其数据结构是栈的形式。示例以下:app
>>> ls = LocalStack() >>> ls.push(42) >>> ls.top 42 >>> ls.push(23) >>> ls.top 23 >>> ls.pop() 23 >>> ls.top 42
LocalStack在Flask框架中会频繁的出现,其Request Context和App Context的实现都是基于LocalStack,具体能够参考Github上的Flask源码框架
LocalProxy用于代理Local对象和LocalStack对象,而所谓代理就是做为中间的代理人来处理全部针对被代理对象的操做,以下图所示:
接下来咱们将重点讲下以下内容:
初始化LocalProxy有三种方式:
__call__
methodfrom werkzeug.local import Local l = Local() # these are proxies request = l('request') user = l('user') from werkzeug.local import LocalStack _response_local = LocalStack() # this is a proxy response = _response_local()
上述代码直接将对象像函数同样调用,这是由于Local和LocalStack都实现了__call__
method,这样其对象就是callable的,所以当咱们将对象做为函数调用时,实际调用的是__call__
method,能够看下本文开头部分的Local的源代码,会发现__call__
method会返回一个LocalProxy对象
l = Local()
request = LocalProxy(l, 'request')
实际上这段代码跟第一种方式是等价的,但这种方式是最'原始'的方式,咱们在Local的源代码实现中看到其__call__
method就是经过这种方式生成LocalProxy的
request = LocalProxy(get_current_request())
经过传递一个函数,咱们能够自定义如何返回Local或LocalStack对象
那么LocalProxy是如何实现这种代理的呢?接下来看下源码解析
下面截取LocalProxy的部分代码,咱们来进行解析
# 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__'): # "local" is a callable that is not an instance of Local or # LocalManager: mark it as a wrapped function. 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. """ # 因为全部Local或LocalStack对象都有__release_local__ method, 因此若是没有该属性就代表self.__local为callable对象 if not hasattr(self.__local, '__release_local__'): return self.__local() try: # 此处self.__local为Local或LocalStack对象 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 def __delitem__(self, key): del self._get_current_object()[key] if PY2: __getslice__ = lambda x, i, j: x._get_current_object()[i:j] def __setslice__(self, i, j, seq): self._get_current_object()[i:j] = seq def __delslice__(self, i, j): del self._get_current_object()[i:j] # 截取部分操做符代码 __setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v) __delattr__ = lambda x, n: delattr(x._get_current_object(), n) __str__ = lambda x: str(x._get_current_object()) __lt__ = lambda x, o: x._get_current_object() < o __le__ = lambda x, o: x._get_current_object() <= o __eq__ = lambda x, o: x._get_current_object() == o
__init__
method中传递的local
参数会被赋予属性_LocalProxy__local
,该属性能够经过self.__local
进行访问,关于这一点能够看StackOverflow的问题回答_get_current_object
来获取代理的对象。须要注意的是当初始化参数为callable对象时,则直接调用以返回Local或LocalStack对象,具体看源代码的注释。_get_current_object
method来获取真正代理的对象,而后再进行相应操做但是说了这么多,为何必定要用proxy,而不能直接调用Local或LocalStack对象呢?这主要是在有多个可供调用的对象的时候会出现问题,以下图:
咱们再经过下面的代码也许能够看出一二:
# use Local object directly from werkzeug.local import LocalStack user_stack = LocalStack() user_stack.push({'name': 'Bob'}) user_stack.push({'name': 'John'}) def get_user(): # do something to get User object and return it return user_stack.pop() # 直接调用函数获取user对象 user = get_user() print user['name'] print user['name']
打印结果是:
John John
再看下使用LocalProxy
# use LocalProxy from werkzeug.local import LocalStack, LocalProxy user_stack = LocalStack() user_stack.push({'name': 'Bob'}) user_stack.push({'name': 'John'}) def get_user(): # do something to get User object and return it return user_stack.pop() # 经过LocalProxy使用user对象 user = LocalProxy(get_user) print user['name'] print user['name']
打印结果是:
John Bob
怎么样,看出区别了吧,直接使用LocalStack对象,user一旦赋值就没法再动态更新了,而使用Proxy,每次调用操做符(这里[]操做符
用于获取属性),都会从新获取user,从而实现了动态更新user的效果。见下图:
Flask以及Flask的插件不少时候都须要这种动态更新的效果,所以LocalProxy就会很是有用了。