Python标准库中有一个叫做 thread locals
的东西,一个local对象就是一个线程安全和线程隔离的全局变量。也就是说,不管你是往local对象写数据还是读数据,该local对象都会获取当前程序所处的线程,并提取该线程相应的数据。然而,这种做法有一些缺陷。除了多线程,我们还会使用其它的并发方式。一种很流行的做法就是使用greenlets
(协程)。
werkzeug.local
的出现就是为了解决这个问题的。werkzeug.local
和thread local
有着类似的功能,但对greenlets
也起作用。直奔源码,看看这到底是怎么实现的:
# part of werkzeug.local
class Local(object):
__slots__ = ('__storage__', '__ident_func__')
def __init__(self):
object.__setattr__(self, '__storage__', {})
object.__setattr__(self, '__ident_func__', get_ident)
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}
# ......
Local有两个属性__storage__
,__ident_func__
。__storage__
用来存储数;__ident_func__
的值为get_ident
,从下面代码可以看出该函数是用来获取当前线程ID或者greenlet对象。(简单起见,下文中线程、协程、greenlet对象均表示同一意思)
try:
from greenlet import getcurrent as get_ident
except ImportError:
try:
from thread import get_ident
except ImportError:
from _thread import get_ident
werkzeug.local实现线程数据安全的关键是重写了__setattr__
和__getattr__
这两个两个魔术方法。重写了这两个魔术方法之后。l.some_attr
(l=Local()
)就不再是它看上去那么简单了:
get_ident
方法获取当前线程/协程IDsome_attr
属性进行读写l.some_attr
相当于l.__storage__[get_ident()].some_attr
简单说,werkzeug就是在Local中构建一个内部字典__storage__
用来保存线程资源,字典的key是线程/协程ID,字典value就是对应线程/协程的资源(资源也是以key-value对的字典形式表示)。但是直接通过线程ID这个key访问相应的资源就显得太丑了,werkzeug重写了__setattr__
等魔术方法很巧妙地解决了这个问题。
werkzeug还定义LocalStack
、LocalProxy
等类,这些类在web框架设计中也很常用。
LocalStack,顾名思义
a local object with a stack instead
也就是说,LocalStack和Local的工作方法差不多一样,Local以属性的形式存取线程资源,而LocalStack是以栈的形式存取线程资源。看看例子就明白了:
LocalStack源码如下,
# part of werkzeug.local
class LocalStack(object):
def __init__(self):
self._local = Local()
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
# ......
LocalStack的实现比较简单。
LocalProxy,顾名思义,
a localproxy acts as a proxy for a werkzeug local
关于代理,可以参考这几篇文章,它们很好地向我们阐述了代理模式。
一个LocalProxy对象就是一个Local对象的代理。我们对LocalProxy对象的操作都会转发到Local对象上。来看看例子吧:
上面,我们创建了一个Local对象l
,给l
添加个request
属性;然后创建一个LocalProxy对象lp
,代理到l
的request
上。我们对lp
的操作都实际上作用到l.request
上。
LocalProxy除了可以代理Local对象,还可以代理其它对象,只需将对象工厂函数传递给LocalProxy构造函数即可。见下:
那么,这个ProxyPython是怎么实现的呢?答案就在源码里:
@implements_bool
class LocalProxy(object):
__slots__ = ('__local', '__dict__', '__name__')
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__)
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
__setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
__getitem__ = lambda x, i: x._get_current_object()[i]
__iter__ = lambda x: iter(x._get_current_object())
__contains__ = lambda x, i: i in x._get_current_object()
__add__ = lambda x, o: x._get_current_object() + o
__sub__ = lambda x, o: x._get_current_object() - o
# ......
__init__
中,通过__setattr__
函数,LocalProxy对象的_LocalProxy__local
被设置为local,这个属性的名字比较奇怪,可以简单的认为为self.__local = local
(具体可以参考这里)_get_current_object
获取代理的对象,在这个方法中,proxy通过判断代理对象是否含有__release_local__
属性来判断代理对象是一个werkzeug Local
对象还是一个普通对象。_get_current_object
获取的代理对象上。至此,我们通过阅读源码,以及提供简单的例子,学习了werkzeug.local
中主要的类。先总结如下:
Local
:和标准库threading.local
功能类似,但对greenlet也起作用LocalStack
:和Local
类似,但一般通过栈访问线程资源LocalProxy
:一般涌来代理Local
对象The end.