在咱們使用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就會很是有用了。