在我们使用Flask以及Werkzeug框架的过程中,经常会遇到如下三个概念:Local、LocalStack和LocalProxy。尤其在学习Flask的Request Context和App Context的过程中,这几个概念出现的更加频繁,另外很多Flask插件都会使用这三个概念对应的技术。那么这三个东西到底是什么?我们为什么需要它们?以及如何使用呢?本篇文章主要就是来解答这些问题。
Local
这部分我们重点介绍Local概念,主要分为以下几个部分:
- 为什么需要Local?
- Local的使用
- Local的实现
为什么需要Local?
在Python的标准库中提供了thread local
对象用于存储thread-safe和thread-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应用中可能会存在如下问题:
- 有些应用使用的是greenlet协程,这种情况下无法保证协程之间数据的隔离,因为不同的协程可以在同一个线程当中。
- 即使使用的是线程,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