Flask上下文源码分析(一)

flask中的上下文分两种,application context和request context,即应用上下文和请求上下文。
 
从名字上看,可能会有误解,认为应用上下文是一个应用的全局变量,全部请求均可以访问修改其中的内容;而请求上下文则是请求内可访问的内容。
但事实上,这二者并非全局与局部的关系,它们都处于一个请求的局部中。
 
先说结论每一个请求的g都是独立的,而且在整个请求内都是可访问修改的。
 
下面来研究一下。
 
上下文类的定义:
 
上下文类定义在flask.ctx模块中
 
class AppContext(object):
    def __init__(self, app):
        self.app = app
        self.url_adapter = app.create_url_adapter(None)
        self.g = app.app_ctx_globals_class()

        # Like request context, app contexts can be pushed multiple times
        # but there a basic "refcount" is enough to track them.
        self._refcnt = 0
 
查看了源代码,AppContext类便是应用上下文,能够看到里面只保存了几个变量,其中比较重要的有:
app是当前web应用对象的引用,如Flask;还有g,用来保存须要在每一个请求中须要用到的请求内全局变量。
 
 
class RequestContext(object):
    def __init__(self, app, environ, request=None):
        self.app = app
        if request is None:
            request = app.request_class(environ)
        self.request = request
        self.url_adapter = app.create_url_adapter(self.request)
        self.flashes = None
        self.session = None

        # Request contexts can be pushed multiple times and interleaved with
        # other request contexts.  Now only if the last level is popped we
        # get rid of them.  Additionally if an application context is missing
        # one is created implicitly so for each level we add this information
        self._implicit_app_ctx_stack = []
 
RequestContext即请求上下文,其中有咱们熟悉的request和session,app和应用上下文中的app含义相同。
 
上下文对象的做用域
 
那么这两种上下文运行时是怎么被使用的呢?
 
线程有个叫作ThreadLocal的类,也就是一般实现线程隔离的类。而werkzeug本身实现了它的线程隔离类:werkzeug.local.Local。LocalStack就是用Local实现的。
 
在flask.globals模块中定义了两个LocalStack对象:
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
 
LocalStack是flask定义的线程隔离的栈存储对象,分别用来保存应用和请求上下文。
它是线程隔离的意思就是说,对于不一样的线程,它们访问这两个对象看到的结果是不同的、彻底隔离的。这是根据pid的不一样实现的,相似于门牌号。
 
而每一个传给flask对象的请求,都是在不一样的线程中处理,并且同一时刻每一个线程只处理一个请求。因此对于每一个请求来讲,它们彻底不用担忧本身上下文中的数据被别的请求所修改。
 
而后就能够解释这个特性:从flask模块中引入的g、session、request、current_app是怎么作到同一个对象能在全部请求中使用而且不会冲突。
 
这几个对象仍是定义在flask.globals中:
current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))
g = LocalProxy(partial(_lookup_app_object, 'g'))
 
LocalProxy类的构造函数接收一个callable参数,上面这几个就传入了一个偏函数。以g为例,当对g进行操做时,就会调用做为参数的偏函数,并把操做转发到偏函数返回的对象上。
 
查看这几个函数的实现:
def _lookup_req_object(name):
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError('working outside of request context')
    return getattr(top, name)


def _lookup_app_object(name):
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError('working outside of application context')
    return getattr(top, name)


def _find_app():
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError('working outside of application context')
    return top.app
 
因为_app_ctx_stack和_request_ctx_stack都是线程隔离的,因此对g的调用就是这样一个过程:
 
访问g-->从当前线程的应用上下文栈顶获取应用上下文-->取出其中的g对象-->进行操做。
因此能够经过一个g对象而让全部线程互不干扰的访问本身的g。
 
上下文对象的推送
 
构建Flask对象后并不会推送上下文,而在Flask对象调用run()做为WSGI 应用启动后,每当有请求进入时,在推送请求上下文前,若是有必要就会推送应用上下文。但运行了run就会阻塞程序,因此在shell中调试时,必须手动推送上下文;或者使用flask-scripts,它运行的任务会在开始时自动推送。
 
上面加粗的“若是有必要”,那么什么叫有必要呢?是否是意味着在每一个线程里应用上下文只会被推送一次、一次请求结束下一次请求来的时候就不用再推送应用上下文了呢?
来看RequestContext的源码,push函数:
def push(self):    
    top = _request_ctx_stack.top
    if top is not None and top.preserved:
        top.pop(top._preserved_exc)

    # Before we push the request context we have to ensure that there
    # is an application context.
    app_ctx = _app_ctx_stack.top
    if app_ctx is None or app_ctx.app != self.app:
        app_ctx = self.app.app_context()
        app_ctx.push()
        self._implicit_app_ctx_stack.append(app_ctx)
    else:
        self._implicit_app_ctx_stack.append(None)

    _request_ctx_stack.push(self)
 
flask在推送请求上下文的时候调用push函数,他会检查当前线程的应用上下文栈顶是否有应用上下文;若是有,判断与请求上下文是否属于同一个应用。在单WSGI应用的程序中,后者的判断无心义。
此时,只要没有应用上下文就会推送一个当前应用的上下文,而且把该上下文记录下来。
 
请求处理结束,调用auto_pop函数,其中又调用自身的pop函数:
def pop(self, exc=None): 
   app_ctx = self._implicit_app_ctx_stack.pop()
         ………………      
      ………………
   rv = _request_ctx_stack.pop()   
    # Get rid of the app as well if necessary.
    if app_ctx is not None:
        app_ctx.pop(exc)
 
会把请求上下文和应用上下文都pop掉。
 
故,在单WSGI应用环境下,每一个请求的两个上下文都是彻底独立的(独立于线程上曾经的请求,独立于其余线程的请求)。Q.E.D
 
那么,何时不必推送呢?事实上,每次请求到来的时候都会推送,都是有必要的。由于当Flask在做为WSGI应用运行的时候,不可能出现当前线程的应用上下文已存在的状况。
 
那么就要搞清何时会有已存在的应用上下文。
 
该博文在最后提到了“两个疑问”:①应用和请求上下文在运行时都是线程隔离的,为什么要分开来?②每一个线程同时只处理一个请求,上下文栈确定只有一个对象,为什么要用栈来存储?
博主认为,这两个设计都是为了在离线状态下调试用:
 
 
因此,综上所述,在非离线状态下,上下文栈在每一个WSGI应用里是独立的,而每一个应用里线程同时只处理一个请求,故上下文栈确定只有一个对象。而且,在请求结束后都会释放,因此新的请求来的时候都会从新推送两个上下文。
 
小结:
解释了这么多,对于flask编程来讲,只有一个应用上的结论:每一个请求的g都是独立的,而且在整个请求内都是可访问修改的。
 
ps.原本只是想知道可否在请求中保存一个变量,就研究了g的生存周期和做用范围,最后花了5个小时左右读了flask英文文档、各类博文和源代码,写了这些文字。我这对于细枝末节的东西吹毛求疵的精神真是害苦了我。。。
 
附上一些其余的笔记:
 
全局变量g,会在每次请求到来时重置
flask. g

Just store on this whatever you want. For example a database connection or the user that is currently logged in.web

 
Starting with Flask 0.10 this is stored on the application context and no longer on the request context which means it becomes available if only the application context is bound and not yet a request
这个意思是g在应用上下文,而不是请求上下文。只要push了应用上下文就可使用g对象
不要误解为g是整个程序内共享的
 
 
The application context is created and destroyed as necessary. It never moves between threads and it will not be shared between requests.
 
推送程序上下文:app = Flask(xxx),     app.app_context().push() 推送了程序上下文,g可使用,当前线程的current_app指向app
相关文章
相关标签/搜索