原创作者:flowell,转载请标明出处:https://www.cnblogs.com/flowell/p/local_local_proxy_local_stack.html


Local是什么?

无论你接触到的是threading.Local还是werkzeug.Local,它们都代表一种变量——每个线程自己的全局变量。


全局变量,一般位于进程的堆上。一个进程的所有线程都可以访问同一个全局变量,因为它们共享着进程的地址空间,所以每一个线程都可以访问,这也带来了问题,如果多个线程同时访问同一个变量,会对该变量的读写造成不可预估的结果,所以通常我们会使用锁或其他的同步机制来控制线程之间对共享变量的访问。当然了,这不是本文关注的地方。

说回Local,我们在开头提到Local是线程自己的全局变量。所谓线程自己的,就是说该“全局变量”只有拥有的线程自己可以访问,对于其它的线程是不可见的。怎么理解这个定义呢?我们先来看一种场景:函数A处理完参数A,函数B要处理函数A处理过的参数A,那么函数A就要把参数A传递给函数B。如果函数C也要接着处理这个参数呢,函数D也要呢?那么参数A就要在这些函数之间不断地传递,这些函数生命时也要提前声明好参数。可想而知,如果有参数要在函数之间传递,那么函数会变得很复杂,调用函数也很复杂。有没有简便的办法呢?

其实我们在函数间传递参数,为的是要使这个参数对于需要的函数都可视,那么将它变成一个全局变量不就得了?可是变成全局变量的话,其它的线程就会访问到我这个全局变量,可能改变它的值,这不是本线程的本意,我只想一个人独占它。这时,我们就需要一种变量,对于本线程而言,它应该是一个全局变量,对于进程的其它线程而言,它又像是一个局部变量。这就是使用Local的一种场景了,Local就是这样一种变量。

如果在全局域定义了一个Local,那么这个local其实并不是一个全局变量,每个线程访问这个变量时,拿到的实际上都是本线程对应的Local。怎么实现这种效果呢?其实很简单,Local本身并不是一个变量,它还包含了一些操作。你可以这样理解,每个进程都有一个全局的字典,每个线程本身有自己的线程ID,进程的所有线程都可以访问这个全局的字典,那么它们把自己的线程ID当做字典的key,把需要存储的东西当做value,每个线程只能通过自己的key来访问这个字典,那么value本身就相当于一个线程独占的全局变量啦!是不是?每个线程都怪怪地拿属于自己的东西,一个全局的东西,这就相当于一个线程内部的全局变量。具体的代码实现有所区别,但大体上是这个思路。

class Local(object):
__slots__ = ('__storage__', '__ident_func__') def __init__(self):
object.__setattr__(self, '__storage__', {}) # 存放东西的全局字典
object.__setattr__(self, '__ident_func__', get_ident) # 每个线程的key def __iter__(self):
return iter(self.__storage__.items()) def __call__(self, proxy):
"""Create a proxy for a name."""
return LocalProxy(self, proxy)    # 这里返回一个LocalProxy对象,LocalProxy是一个代理,代理Local对象。 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)

  

Local怎么用?

伪代码如下

local = Local()
local.request = "i am a request"
local.response = "i am a response" def work():
local.request = xxxx # 每个线程都只会访问到属于自己的request和response
local.response = xxxx # 就算改变response,也只是改变本线程的值 if __name__ == "__main__":
for i in range(10):
Thread(target=work).start()

 

通过声明一个全局的Local对象,然后像访问对象的属性一样访问你要保留的值。你可以这样理解,Local相当于一个字典,我要通过自己定义的key,来访问我需要的值,即调用local.key来获取值。这样使用起来其实很别扭,明明我是定义一个值,却变成像是访问一个对象的属性一样,写起来很奇怪,有时候也不好理解。能不能像定义一个全局变量一样,直接使用一个Local变量呢?

# 我想要这种效果
request = "i am a request"
response = "i am a response"

Local的__call__方法就是干这件事的,使用__call__方法,我们可以让一个Local变得看起来像一个全局变量。

# 你只需要调用Local对象的__call__方法
local = Local()
local.request = "i am a request"
my_request = local("request") # 注意,这里传入的字符串需要和上面保存时的一致
my_request  # "i am a request"

  

my_request现在等同于local.request,比起local.request,my_request是不是看起来更像一个全局变量?但记住,它是一个“线程独有的全局变量”。

LocalProxy是什么?

local相当于一个字典,local.x的x相当于key,而LocalProxy代管了这把key和local,我们只需访问LocaProxy本身,它自动用这把key去local字典查到值,返回给我们,这就是代理(proxy)


my_request实际上是一个LocalProxy,直接访问my_request,它是一个"i am a request"字符串。前面我们提到Local对象可以通过local.xxx=value来存储我需要的本地全局变量,这样的local对象看起来就像一个字典,可以存储任意的值。但是每次都通过local.xxx来获取我们想要的值太麻烦了,我们需要一个对象来帮我们完成这个重复性的动作,把key交给它,把字典交给它,我只要访问它,它就通过key去字典中查值,然后把值返回给我。这样子它对于我来说就像存储的值本身一样。这就是代理。

LocalProxy的原理就是这样,它帮我们干了到Local中查找值的方法,所以我们要把存储local.xxx时的“xxx”这把打开local的key告诉代理,然后把local本身也告诉代理,这样LocalProxy便有了钥匙,和要打开的门,自然他就可以把门里面的东西返回给我们了。从这个角度考虑,Local本身也可以看做是一个代理,它代理的是线程的全局变量,而它持有的key则是线程的id,它会通过id到全局的dict中查找本线程的全局变量,然后返回给我们。

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.
"""
if not hasattr(self.__local, '__release_local__'):
return self.__local()
try:
return getattr(self.__local, self.__name__)      # 通过key(name)到字典(local)中获取value
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 __repr__(self):
try:
obj = self._get_current_object()
except RuntimeError:
return '<%s unbound>' % self.__class__.__name__
return repr(obj) def __bool__(self):
try:
return bool(self._get_current_object())
except RuntimeError:
return False def __unicode__(self):
try:
return unicode(self._get_current_object()) # noqa
except RuntimeError:
return repr(self) def __dir__(self):
try:
return dir(self._get_current_object())
except RuntimeError:
return [] def __getattr__(self, name):
if name == '__members__':
return dir(self._get_current_object())
return getattr(self._get_current_object(), name)  # 通过key(name)到字典(local)中去查找真正的value,并返回 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)

 LocalProxy中有许多方法,这些方法都是LocalProxy本身实现的一些通用的方法,这些方法不是对本身的调用,而是对代理值的调用。

我们也可以不调用Local的__call__方法构造LocalProxy,可以直接通过LocalProxy的构造函数构造一个LocalProxy,实质上是一样的。

local = Local()
local.request = "request"
my_request = LocalProxy(local, "request") # 第二个参数要和local.xxx的xxx相同

  

LocalStack是什么?

LocalStack和Local差不多,只不过Local像一个字典。LocalStack则是一个栈,存储数据的方式不太一样。可以认为它是一个线程独有的一个全局栈。使用它不用担心被进程的其它线程干扰。

class LocalStack(object):
def __init__(self):
self._local = Local() def __release_local__(self):
self._local.__release_local__() def _get__ident_func__(self):
return self._local.__ident_func__ def _set__ident_func__(self, value):
object.__setattr__(self._local, '__ident_func__', value)
__ident_func__ = property(_get__ident_func__, _set__ident_func__)
del _get__ident_func__, _set__ident_func__ 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):
"""Pushes a new item to the stack"""
rv = getattr(self._local, 'stack', None)
if rv is None:
self._local.stack = rv = []
rv.append(obj)
return rv def pop(self):
"""Removes the topmost item from the stack, will return the
old value or `None` if the stack was already empty.
"""
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):
"""The topmost item on the stack. If the stack is empty,
`None` is returned.
"""
try:
return self._local.stack[-1]
except (AttributeError, IndexError):
return None

 

Local和线程安全的区别

Local并不代表着线程安全(Thread-Safe),线程安全更多的是强调多个线程访问同一个全局变量时的同步机制,而Local代表的全局变量时线程独占的,对于其他线程而言是不可见的,所以根本不存在线程安不安全的问题。Local永远只会被本线程操作,所以如果硬是要下一个定义,那么是线程安全的。

Flask解析(一):Local、LocalStak、LocalProxy的更多相关文章

  1. Flask补充--threading.local对象

    目录 Local 局部变量 全局变量 使用threading.local() 自定义threading.local 函数版 面向对象版 通过setattr和getattr实现 每个对象有自己的存储空间 ...

  2. Flask 解析 Web 端 请求 数组

    Web前台由 JavaScript 通过Ajax发送POST请求,当请求数据为数组时,Python Flask 做服务器时的解析如下: js: var ids = []; for (var i = 0 ...

  3. Flask解析(二):Flask-Sqlalchemy与多线程、多进程

    Sqlalchemy flask-sqlalchemy的session是线程安全的,但在多进程环境下,要确保派生子进程时,父进程不存在任何的数据库连接,可以通过调用db.get_engine(app= ...

  4. 六十七:flask上下文之Local线程隔离对象

    Local对象在flask中,类似于request对象,其实是绑定到了werkzeug.local.Local对象上,这样即使是同一个对象,在多线程中都是隔离的,类似的对象还有session以及g对象 ...

  5. Flask之Local、LocalStack和LocalProxy

    在我们使用Flask以及Werkzeug框架的过程中,经常会遇到如下三个概念:Local.LocalStack和LocalProxy.尤其在学习Flask的Request Context和App Co ...

  6. flask基础之LocalProxy代理对象(八)

    前言 flask框架自带的代理对象有四个,分别是request,session,g和current_app,各自的含义我们在前面已经详细分析过.使用代理而不是显式的对象的主要目的在于这四个对象使用太过 ...

  7. Flask源码关于local的实现

    flask源码关于local的实现 try: # 协程 from greenlet import getcurrent as get_ident except ImportError: try: fr ...

  8. flask LOCAL线程隔离技术

    from threading import Thread from werkzeug.local import Local local = Local()#实例化一个线程隔离对象 request = ...

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

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

随机推荐

  1. Scrapy项目 - 实现斗鱼直播网站信息爬取的爬虫设计

    要求编写的程序可爬取斗鱼直播网站上的直播信息,如:房间数,直播类别和人气等.熟悉掌握基本的网页和url分析,同时能灵活使用Xmind工具对Python爬虫程序(网络爬虫)流程图进行分析.   一.项目 ...

  2. ViewModel 和 ViewModelProvider.Factory:ViewModel 的创建者

    本文翻译自:https://medium.com/koderlabs/viewmodel-with-viewmodelprovider-factory-the-creator-of-viewmodel ...

  3. 《你不知道的JavaScript》笔记(一)

    用了一个星期把<你不知道的JavaScript>看完了,但是留下了很多疑惑,于是又带着这些疑惑回头看JavaScript的内容,略有所获. 第二遍阅读这本书,希望自己能够有更为深刻的理解. ...

  4. 转载:Docker入门只需看这一篇就够了

    最近项目中需要用到 Docker 打包,于是上网查找资料学习了 Docker 的基本命令,记录一下自己遇到的一些错误. 准备开始自己写,结果看到了阮一峰老师的文章,瞬间就没有写下去的动力了,转载大佬的 ...

  5. mysql数据库设计规则总结

    MySQL数据库设计总结   规则1:一般情况可以选择MyISAM存储引擎,如果需要事务支持必须使用InnoDB存储引擎. 注意:MyISAM存储引擎 B-tree索引有一个很大的限制:参与一个索引的 ...

  6. 【算法随记五】使用FFT变换自动去除图像中严重的网纹。

    这个课题在很久以前就已经有所接触,不过一直没有用代码去实现过.最近买了一本<机器视觉算法与应用第二版>书,书中再次提到该方法:使用傅里叶变换进行滤波处理的真正好处是可以通过使用定制的滤波器 ...

  7. Zookeeper监控(Zabbix)

      一直在弄监控,这些个中间件Zookeeper.Kafka......,平时也只知道一点皮毛,也就搭建部署过,没有真正的用过,一般都是大数据的同学在用,作为运维人员我需要对他做一个监控,由于对他不是 ...

  8. SpringBoot返回JSON

    目录 1.SpringBoot返回JSON简介 2.整合jackson-databind 3.整合Gson 4.整合fastjson 1.SpringBoot返回JSON简介 随着web开发前后端分离 ...

  9. 使用echarts画一个类似组织结构图的图表

    昨天,写了一篇关于圆环进度条的博客(请移步:Vue/React圆环进度条),已经烦不胜烦,今天又遇到了需要展示类似公司的组织结构图的功能需求,要冒了!!! 这种需求,自己用div+css也是可以实现的 ...

  10. 爬虫之beautifulsoup篇之一

    一个网页的节点太多,一个个的用正则表达式去查找不方便且不灵活.BeautifulSoup将html文档转换成一个属性结构,每个节点都是python对象.这样我们就能针对每个结点进行操作.参考如下代码: ...