Werkzeug Local與LocalProxy等淺析

首先貼出官方文檔地址:http://werkzeug.pocoo.org/doc...
幾個local?
threading.local
werkzeug.local模塊中的:
Local
LocalStack
LocaProxyjava

why not threading.local?python

threading.local,之前接觸過java的,對這個再熟悉不過了。線程局部變量,也就是每一個線程的私有變量,具備線程隔離性。服務器

按咱們正常的理解,應該是每個http請求對應一個處理線程。那麼這樣看來使用threading.local應該夠了,爲何werkzeug還本身搞了一套?裝逼?非也。session

在python中,除了線程以外,還有個叫協程的東東,(這裏不提進程)。java中貌似是沒法實現協程的。而python的協程感受高大尚的樣子,python3.5開始對協程內置支持,並且也有相關開源庫greenlet等。app

協程是什麼?
舉個例子,好比一個線程在處理IO時,該線程是處於空閒狀態的,等待IO返回。可是此時若是不讓咱們的線程乾等着cpu時間片耗光,有沒有其餘辦法,解決思路就是採用協程處理任務,一個線程中能夠運行多個協程,噹噹前協程去處理IO時,線程能夠立刻調度其餘協程繼續運行,而不是乾等着不幹活。ide

這麼一說,咱們知道了協程會複用線程,WSGI不保證每一個請求必須由一個線程來處理,若是WSGI服務器不是每一個線程派發一個請求,而是每一個協程派發一個請求,因此若是使用thread local變量可能會形成請求間數據相互干擾,由於一個線程中存在多個請求。
因此werkzeug給出了本身的解決方案:werkzeug.local模塊。函數

from werkzeug.local import Local, LocalManager

local = Local()
local_manager = LocalManager([local])

def application(environ, start_response):
    local.request = request = Request(environ)
    ...

application = local_manager.make_middleware(application)

Local配合LocalManager會確保無論是協程仍是線程,只要當前請求處理完成以後清除Local中對應的內容。this

>>> loc = Local()
>>> loc.foo = 42
>>> release_local(loc)
>>> hasattr(loc, 'foo')

固然,你也能夠調用werkzeug.local.release_local(local)手動釋放Local或者LocalStack ,可是不能清除代理對象LocalProxy(代理對象底層保留了對Local對象的引用,以便在以後釋放)的數據。線程

>>> ls = LocalStack()
>>> ls.push(42)
>>> ls.top
42
>>> ls.push(23)
>>> ls.top
23
>>> ls.pop()
23
>>> ls.top

LocalStack,與Local相似,可是管理數據的方式是採用棧的方式,能夠經過LocalManager對象強制釋放,可是不建議這麼作,而是經過其pop方法彈出。代理

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()

werkzeug.local.LocalProxy:Local對象的一個代理。若是你須要建立Local或LocalStack對象的代理,能夠直接call。

session = LocalProxy(lambda: get_current_request().session)

from werkzeug.local import Local, LocalProxy
local = Local()
request = LocalProxy(local, 'request')

>>> from werkzeug.local import LocalProxy
>>> isinstance(request, LocalProxy)
True

你也能夠經過LocalProxy構造一個代理對象,參數爲能夠調用的對象或者函數。
_get_current_object()返回被代理的對象。

werkzeug.local模塊關鍵部分代碼:

import copy
from functools import update_wrapper
from werkzeug.wsgi import ClosingIterator
from werkzeug._compat import PY2, implements_bool
try:
    from greenlet import getcurrent as get_ident
except ImportError:
    try:
        from thread import get_ident
    except ImportError:
        from _thread import get_ident


def release_local(local):
    local.__release_local__()


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)


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):
        rv = getattr(self._local, 'stack', None)
        if rv is None:
            self._local.stack = rv = []
        rv.append(obj)
        return rv

    def pop(self):
        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):
        try:
            return self._local.stack[-1]
        except (AttributeError, IndexError):
            return None


class LocalManager(object):

    def cleanup(self):
        for local in self.locals:
            release_local(local)

    def make_middleware(self, app):
        def application(environ, start_response):
            return ClosingIterator(app(environ, start_response), self.cleanup)
        return application


@implements_bool
class LocalProxy(object):

    def __init__(self, local, name=None):
        object.__setattr__(self, '_LocalProxy__local', local)
        object.__setattr__(self, '__name__', name)

    def _get_current_object(self):
        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__)
相關文章
相關標籤/搜索