Werkzeug框架

在咱们使用Flask以及Werkzeug框架的过程当中,常常会遇到以下三个概念:Local、LocalStack和LocalProxy。尤为在学习Flask的Request Context和App Context的过程当中,这几个概念出现的更加频繁,另外不少Flask插件都会使用这三个概念对应的技术。那么这三个东西究竟是什么?咱们为何须要它们?以及如何使用呢?本篇文章主要就是来解答这些问题。python

Local

这部分咱们重点介绍Local概念,主要分为如下几个部分:git

  • 为何须要Local?
  • Local的使用
  • Local的实现

为何须要Local?

在Python的标准库中提供了thread local对象用于存储thread-safethread-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

  1. 有些应用使用的是greenlet协程,这种状况下没法保证协程之间数据的隔离,由于不一样的协程能够在同一个线程当中。
  2. 即便使用的是线程,WSGI应用也没法保证每一个http请求使用的都是不一样的线程,由于后一个http请求可能使用的是以前的http请求的线程,这样的话存储于thread local中的数据多是以前残留的数据。

为了解决上述问题,Werkzeug开发了本身的local对象,这也是为何咱们须要Werkzeug的local对象安全

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对象须要经过LocalManager来管理,初次生成LocalManager对象须要传一个list类型的参数,list中是Local对象,当有新的Local对象时,能够经过local_manager.locals.append()来添加。而当LocalManager对象清理的时候会将全部存储于locals中的当前context的数据都清理掉
  • 上例中当local.request被赋值以后,其能够在当前context中做为全局数据使用
  • 所谓当前context(the same context)意味着是在同一个greenlet(若是有)中,也就确定是在同一个线程当中

那么Werkzeug的Local对象是如何实现这种在相同的context环境下保证数据的全局性和隔离性的呢?bash

Local的实现

咱们先来看下源代码数据结构

# 在有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)来分别存储数据。
  • 当咱们须要释放local数据的内存时,能够经过调用release_local()函数来释放当前context的local数据,以下
>>> loc = Local() >>> loc.foo = 42 >>> release_local(loc) # release_local实际调用local对象的__release_local__ method >>> hasattr(loc, 'foo') False 

LocalStack

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 
  • 从示例看出Local对象存储的时候是相似字典的方式,须要有key和value,而LocalStack是基于栈的,经过push和pop来存储和弹出数据
  • 另外,当咱们想释放存储空间的时候,也能够调用release_local()

LocalStack在Flask框架中会频繁的出现,其Request Context和App Context的实现都是基于LocalStack,具体能够参考Github上的Flask源码框架

LocalProxy

LocalProxy用于代理Local对象和LocalStack对象,而所谓代理就是做为中间的代理人来处理全部针对被代理对象的操做,以下图所示:

 
proxy.jpg

接下来咱们将重点讲下以下内容:

  • LocalProxy的使用
  • LocalProxy代码解析
  • 为何要使用LocalProxy

LocalProxy的使用

初始化LocalProxy有三种方式:

  1. 经过Local或者LocalStack对象的__call__ method
from 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对象

  1. 经过LocalProxy类进行初始化
l = Local()
request = LocalProxy(l, 'request') 

实际上这段代码跟第一种方式是等价的,但这种方式是最'原始'的方式,咱们在Local的源代码实现中看到其__call__ method就是经过这种方式生成LocalProxy的

  1. 使用callable对象做为参数
request = LocalProxy(get_current_request())

经过传递一个函数,咱们能够自定义如何返回Local或LocalStack对象

那么LocalProxy是如何实现这种代理的呢?接下来看下源码解析

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的问题回答
  • LocalProxy经过_get_current_object来获取代理的对象。须要注意的是当初始化参数为callable对象时,则直接调用以返回Local或LocalStack对象,具体看源代码的注释。
  • 重载了绝大多数操做符,以便在调用LocalProxy的相应操做时,经过_get_current_object method来获取真正代理的对象,而后再进行相应操做

为何要使用LocalProxy

但是说了这么多,为何必定要用proxy,而不能直接调用Local或LocalStack对象呢?这主要是在有多个可供调用的对象的时候会出现问题,以下图:

 
multiple objects

咱们再经过下面的代码也许能够看出一二:

# 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的效果。见下图:

 
proxy auto select object

Flask以及Flask的插件不少时候都须要这种动态更新的效果,所以LocalProxy就会很是有用了。

做者:geekpy 连接:https://www.jianshu.com/p/3f38b777a621 来源:简书 简书著做权归做者全部,任何形式的转载都请联系做者得到受权并注明出处。
相关文章
相关标签/搜索