第一版:基于函数实现local对象功能
紧接上文,最最直白的实现方式:
import time
from threading import get_ident, Thread
storage = {}
def set(k, v):
ident = get_ident()
if ident in storage:
storage[ident][k] = v
else:
storage[ident] = {k: v}
def get(k):
ident = get_ident()
return storage[ident][k]
def task(arg):
set('val', arg)
time.sleep(2)
v = get('val')
print(v)
for i in range(10):
t = Thread(target=task, args=(i, ))
t.start()
已经可以实现local对象的功能,但是这样的话使用很不方便!
第二版:基于面向对象思想实现local对象功能
(1)2.1版本:
import time
from threading import get_ident, Thread
class Local(object):
storage = {}
def set(self, k, v):
ident = get_ident()
if ident in Local.storage:
Local.storage[ident][k] = v
else:
Local.storage[ident] = {k: v}
def get(self, k):
ident = get_ident()
return Local.storage[ident][k]
if __name__ == '__main__':
obj = Local()
def task(arg):
obj.set('val', arg)
time.sleep(2)
v = obj.get('val')
print(v)
for i in range(10):
t = Thread(target=task, args=(i, ))
t.start()
(2)2.2版本:
- 实现local对象的通过.value进行赋值操作:
import time
from threading import get_ident, Thread
class Local(object):
storage = {}
def __setattr__(self, k, v):
ident = get_ident()
if ident in Local.storage:
Local.storage[ident][k] = v
else:
Local.storage[ident] = {k: v}
def __getattr__(self, k):
ident = get_ident()
return Local.storage[ident][k]
if __name__ == '__main__':
obj = Local()
def task(arg):
obj.val = arg
time.sleep(2)
v = obj.val
print(v)
for i in range(10):
t = Thread(target=task, args=(i, ))
t.start()
(3)2.3版本:
上述代码存在一个很严重的问题:由于storage是定义为Local类的一个类属性,所以不管创建几个Local()对象,用的都是同一个storage来存!
优化点就是:让代码变得可以放同一个storage/不同storage!
如下将storage变为实例属性即可(想放一个storage里就实例化一个Local对象,想放不同storage里就分别实例化对应Local对象即可):
但是上面这会报错,定位到实例化Local对象时报的错,如下:
问题分析:
- 实例化后会触发
init
方法,在里面有个self.storage={}
,就会触发setattr
方法,里面判断走else,所以又触发setattr
…
问题解决:
-
因为Local继承了类object,所以可以使用基类object的
setattr
来触发父类的setattr
来设置storage,这样就在设置实例属性storage的时候绕过了setattr的自定义实现:
分析:
- 上述2.3版本已经实现了threading.local。但是为啥已经有现成的了,我们不直接用,还要自行实现呢?
- 答案很简单,因为threading.local的功能不够用!
比如我们现在要实现为每个协程创建一份空间。上述2.3版本代码只需要更改一处就可以了(下述代码段第三行)!
【greenlet.getcurrent就是获取当前协程的唯一标识】
import time
from threading import Thread
from greenlet import getcurrent as get_ident
class Local(object):
def __init__(self):
# self.storage = {}
object.__setattr__(self, 'storage', {})
def __setattr__(self, k, v):
ident = get_ident()
if ident in self.storage:
self.storage[ident][k] = v
else:
self.storage[ident] = {k: v}
def __getattr__(self, k):
ident = get_ident()
return self.storage[ident][k]
if __name__ == '__main__':
obj = Local()
def task(arg):
obj.val = arg
time.sleep(2)
v = obj.val
print(v)
for i in range(10):
t = Thread(target=task, args=(i, ))
t.start()
到现在,我们已经非常牛逼了,因为这就是flask里的源码:
from flask import globals
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)
会发现这个源码跟上述2.3版本代码一样哦!!!