当前位置:   article > 正文

FLask之Local、LocalStack和LocalProxy介绍

localproxy

一、Local

1.为什么需要Local?
  • 在Python的标准库中提供了thread local对象用于存储thread-safe和thread-specific的数据,通过这种方式存储的数据只在本线程中有效,而对于其它线程则不可见。
	>>from threading import local
	>#实例化local函数
	>>thread_local_data = local()
	>#定义user_name='Jim'
	>>thread_local_data.user_name="Jim"
	>#通过key"user_name"获取value"Jim"
	>>thread_local_data.user_name
	'Jim'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 使用thread local对象虽然可以基于线程存储全局变量,但是在Web应用中可能会存在如下问题:
    1. 有些应用使用的是greenlet协程,这种情况下无法保证协程之间数据的隔离,因为不同的协程可以在同一个线程当中。
    2. 即使使用的是线程,WSGI应用也无法保证每个http请求使用的都是不同的线程,因为后一个http请求可能使用的是之前的http请求的线程,这样的话存储于thread local中的数据可能是之前残留的数据。
  • 为了解决上述问题,Werkzeug开发了自己的local对象,这也是为什么我们需要Werkzeug的local对象。
2.Local的使用
  • 先举一个简单的示例:
	from werkzeug.local import Local, LocalManager
	# 实例化Local函数
	local = Local()
	# 实例化LocalManager函数
	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)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  1. 首先Local对象需要通过LocalManager来管理,初次生成LocalManager对象需要传一个list类型的参数,list中是Local对象,当有新的Local对象时,可以通过local_manager.locals.append()来添加。而当LocalManager对象清理的时候会将所有存储于locals中的当前context的数据都清理掉
  2. 上例中当local.request被赋值之后,其可以在当前context中作为全局数据使用
    所谓当前context(the same context)意味着是在同一个greenlet(如果有)中,也就肯定是在同一个线程当中
  • 那么Werkzeug的Local对象是如何实现这种在相同的context环境下保证数据的全局性和隔离性的呢?
3.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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 这段代码实际是对__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数据,如下
	# 上文已说到,Local()函数内封装的dict,所以loc实例也是字典
	>>> loc = Local()
	># 向loc字典添加“foo=42”;
	>>> loc.foo = 42
	># release_local实际调用local对象的__release_local__方法
	># 即释放并删除loc字典中的数据
	>>> release_local(loc)  
	># hasattr() 函数用于判断对象是否包含对应的属性。
	># 由于loc字典内的数据被释放,所以foo键不存在;
	>>> hasattr(loc, 'foo')
	False
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

二、LocalStack

  • LocalStack与Local对象类似,区别在于其数据结构是栈的形式,而Local是字典的形式。示例如下:
	>>> ls = LocalStack()
	>>> ls.push(42)
	>>> ls.top
	42
	>>> ls.push(23)
	>>> ls.top
	23
	>>> ls.pop()
	23
	>>> ls.top
	42
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 从示例看出Local对象存储的时候是类似字典的方式,需要有key和value,而LocalStack是基于栈的,通过push和pop来存储和弹出数据
  • 另外,当我们想释放存储空间的时候,也可以调用release_local()
LocalStack在Flask框架中会频繁的出现,其"请求上下文"Request Context和"应用上下文"App Context的实现都是基于LocalStack。

三、LocalProxy

  • LocalProxy用于代理Local对象和LocalStack对象,而所谓代理就是作为中间的代理人来处理所有针对被代理对象的操作,如下图所示:
    在这里插入图片描述
1.LocalProxy的使用
  • 初始化LocalProxy有三种方式:
①.通过Local或者LocalStack对象的__call__ 方法

	#———————————————Local设置代理———————————————————————
	from werkzeug.local import Local
	loc = Local()
	
	# these are proxies
	request = loc('request')
	user = loc('user')
	
	#——————————————LocalStack设置代理——————————————————
	
	from werkzeug.local import LocalStack
	_response_local = LocalStack()
	
	# this is a proxy
	response = _response_local()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 上述代码直接将对象像函数一样调用,这是因为Local和LocalStack都实现了__call__方法,这样其对象就是callable的,因此当我们将对象作为函数调用时,实际调用的是__call__ 方法,可以看下本文开头部分的Local的源代码,会发现__call__ 方法会返回一个LocalProxy对象。
②.通过LocalProxy类进行初始化
	loc= Local()
	request = LocalProxy(loc, 'request')
  • 1
  • 2
  • 实际上这段代码跟第一种方式是等价的,但这种方式是最’原始’的方式,我们在Local的源代码实现中看到其__call__ 方法就是通过这种方式生成LocalProxy的。
③.使用callable对象作为参数
request = LocalProxy(get_current_request())
  • 1
  • 通过传递一个函数,我们可以自定义如何返回Local或LocalStack对象。
那么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):
    	'''返回当前对象。如果出于性能原因您一次希望将真实对象放在代理后面,
    	或者因为要将对象传递到不同的上下文,这将很有用。
    	'''
        '''
        1.由于所有Local或LocalStack对象都有__release_local__ method, \
        	所以如果没有该属性就表明self.__local为callable对象。
       	2.当初始化参数为callable对象时,则直接调用以返回Local或LocalStack对象
       	'''
        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
    '''
    重载了绝大多数操作符,以便在调用LocalProxy的相应操作时,
    通过_get_current_object method来获取真正代理的对象,然后再进行相应操作
    '''
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
3.为什么要使用LocalProxy
  • 为什么一定要用proxy,而不能直接调用Local或LocalStack对象呢?这主要是在有多个可供调用的对象的时候会出现问题,如下图:
    在这里插入图片描述
  • 我们再通过下面的代码也许可以看出一二:
# use Local object directly
from werkzeug.local import LocalStack
user_stack = LocalStack()
# 在堆栈中入栈两个键值对,键都命名为name;
user_stack.push({'name': 'Bob'})
user_stack.push({'name': 'John'})

def get_user():
    # 出栈这两个键值对(获取user对象)
    return user_stack.pop()


# 直接调用函数获取user对象
user = get_user()
# 打印name的value值
print user['name']
print user['name']
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 打印结果是:
John
John
  • 1
  • 2
  • 再看下使用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']
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 打印结果是:
John
Bob
  • 1
  • 2
区别:直接使用LocalStack对象,user一旦赋值就无法再动态更新了,而使用Proxy,每次调用操作符(这里[]操作符用于获取属性),都会重新获取user,从而实现了动态更新user的效果。见下图:

在这里插入图片描述

  • Flask以及Flask的插件很多时候都需要这种动态更新的效果,因此LocalProxy就会非常有用了。

参考:https://www.jianshu.com/p/3f38b777a621

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/448778
推荐阅读
相关标签
  

闽ICP备14008679号