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

Local

这部分我们重点介绍Local概念,主要分为以下几个部分:

  • 为什么需要Local?
  • Local的使用
  • Local的实现

为什么需要Local?

在Python的标准库中提供了thread local对象用于存储thread-safethread-specific的数据,通过这种方式存储的数据只在本线程中有效,而对于其它线程则不可见。正是基于这样的特性,我们可以把针对线程全局的数据存储进thread local对象,举个简单的例子

>>from threading import local
>>thread_local_data = local()
>>thread_local_data.user_name="Jim"
>>thread_local_data.user_name
'Jim'

使用thread local对象虽然可以基于线程存储全局变量,但是在Web应用中可能会存在如下问题:

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

为了解决上述问题,Werkzeug开发了自己的local对象,这也是为什么我们需要Werkzeug的local对象

Local的使用

先举一个简单的示例:

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环境下保证数据的全局性和隔离性的呢?

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进行了二次封装),区别在于其数据结构是栈的形式。示例如下:

>>> 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就会非常有用了。

至此,我们针对Local、LocalStack和LocalProxy的概念已经做了详细阐释,如果你觉得文章对你有帮助,不妨点个赞吧!

Flask之Local、LocalStack和LocalProxy的更多相关文章

  1. werkzeug(flask)中的local,localstack,localproxy探究

    1.关于local python中有threading local处理方式,在多线程环境中将变量按照线程id区分 由于协程在Python web中广泛使用,所以threading local不再满足需 ...

  2. flask 中的 werkzeug Local,LocalStack 和 LocalProxy 技术应用

    什么是 Local wsgi 每次请求,会把过程进行抽离无状态话,过程数据存储在本次请求的全局变量中,使用到了Local. Local 作为每次请求的全局命令空间,属于每次请求的私有 LocalSta ...

  3. Werkzeug(Flask)之Local、LocalStack和LocalProxy

  4. flask高级编程 LocalStack 线程隔离

    转:https://www.cnblogs.com/wangmingtao/p/9372611.html   30.LocalStack作为线程隔离对象的意义 30.1 数据结构 限制了某些能力 30 ...

  5. flask 源码专题(十一):LocalStack和Local对象实现栈的管理

    目录 04 LocalStack和Local对象实现栈的管理 1.源码入口 1. flask源码关于local的实现 2. flask源码关于localstack的实现 3. 总结 04 LocalS ...

  6. 04 flask源码剖析之LocalStack和Local对象实现栈的管理

    04 LocalStack和Local对象实现栈的管理 目录 04 LocalStack和Local对象实现栈的管理 1.源码入口 1. flask源码关于local的实现 2. flask源码关于l ...

  7. LocalStack和Local对象实现栈的管理

    flask里面有两个重要的类Local和LocalStack 输入from flask import globals 左键+ctrl点globals进入源码,进去后找57行 flask只会实例化出这两 ...

  8. Flask 的 Context 机制

    转自https://blog.tonyseek.com/post/the-context-mechanism-of-flask/ Flask 的 Context 机制 2014 年 07 月 21 日 ...

  9. flask-admin章节四:flask session的使用

    1. 关于session flask session可能很多人根本都没有使用过,倒是cookie大家可能使用得比较多.flask cookie使用起来比较简单,就两个函数,读取和设置. 具体使用方式如 ...

随机推荐

  1. CentOS7下使用Sentinel实现Redis集群高可用

    Sentinel是Redis官方提供的一种高可用方案(除了Sentinel,Redis Cluster是另一种方案),它可以自动监控Redis master/slave的运行状态,如果发现master ...

  2. Python - Django - ORM F查询和Q查询

    models.py: from django.db import models # 出版社 class Publisher(models.Model): id = models.AutoField(p ...

  3. Java 终于在 Java 8 中引入了 Lambda 表达式。也称之为闭包或者匿名函数。

    本文首发于 blog.zhaochunqi.com 转载请注明 blog.zhaochunqi.com 根据JSR 335, Java 终于在 Java 8 中引入了 Lambda 表达式.也称之为闭 ...

  4. 【ssh连接docker container问题】

    在向docker container执行ssh或scp的时候,应该将docker container的22端口映射出来,然后ssh/scp命令指定映射出来的端口

  5. DevOps - DevOps精要 - 歧途

    前言 如果在实施DevOps的过程中,周围没有一个人支持你,也没有得到领导和团队成员的理解: 如果在采用DevOps的工具和方法之后,难以获得明显的效率提升,甚至得到了不少的消极反馈: 那就需要反省一 ...

  6. (简单实用)Android支付宝商家收款语音播报

    支付宝商家收款时,语音提示:支付宝收款xxx元,当时觉得这东西还挺有趣的,第一时间通知给商家,减少不必要的纠纷,节约时间成本,对商家对用户都挺好的. 在商家版有这样收款播报的功能,我觉得挺好的. 对列 ...

  7. netty 实现心跳检查--断开重连--通俗易懂

    一.心跳介绍 网络中的接收和发送数据都是使用操作系统中的SOCKET进行实现.但是如果此套接字已经断开,那发送数据和接收数据的时候就一定会有问题. 1.心跳机制: 是服务端和客户端定时的发送一个心跳包 ...

  8. Kibana配置安装

    学习网站 https://discuss.elastic.co https://github.com/elastic 配置 server.port: 5601 server.host: "l ...

  9. Python3之字符串格式化format函数详解(下)

    格式限定符 format通过丰富的的“格式限定符”(语法是 {}中带:号)对需要格式的内容完成更加详细的制定. 进制转换 我们可以再限定符中制定不同的字符对数字进行进制转换的格式化,进制对应的表格: ...

  10. 单源最短路——朴素Dijkstra&堆优化版

    朴素Dijkstra 是一种基于贪心的算法. 稠密图使用二维数组存储点和边,稀疏图使用邻接表存储点和边. 算法步骤: 1.将图上的初始点看作一个集合S,其它点看作另一个集合 2.根据初始点,求出其它点 ...