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 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。
相關文章
相關標籤/搜索