Flask进阶(一)——请求上下文和应用上下文完全解答(下)

来源:互联网 发布:office word 2016 mac 编辑:程序博客网 时间:2024/05/21 17:19

上篇对请求上下文进行了详细解答。

在flask的官方文档中,它先介绍应用上下文,再介绍请求上下文。在笔者的安排是先介绍请求上下文,再介绍应用上下文。

如果有了上篇的基础,那么应用上下文也同样很容易理解。先回忆以下globals.py里关于应用上下文的部分:

def _lookup_app_object(name):    top = _app_ctx_stack.top    if top is None:        raise RuntimeError(_app_ctx_err_msg)    return getattr(top, name)def _find_app():    top = _app_ctx_stack.top    if top is None:        raise RuntimeError(_app_ctx_err_msg)    return top.app_app_ctx_stack = LocalStack()current_app = LocalProxy(_find_app)g = LocalProxy(partial(_lookup_app_object, 'g'))

2、应用上下文(current_app, g)

(1)生命周期

from flask import Flask, current_app app = Flask('SampleApp') @app.route('/')def index():    return 'Hello, %s!' % current_app.name
可以通过”current_app.name”来获取当前应用的名称,也就是”SampleApp”。如果还有印象,”current_app”是一个本地代理,它的类型是”werkzeug.local. LocalProxy”,它所代理的即是我们的app对象,也就是说”current_app == LocalProxy(app)”。用”current_app”是因为它也是一个ThreadLocal变量,对它的改动不会影响到其他线程。你可以通过”current_app._get_current_object()”方法来获取app对象。

既然是ThreadLocal对象,那它就只在请求线程内存在,它的生命周期就是在应用上下文里。离开了应用上下文,”current_app”一样无法使用。
app = Flask('SampleApp') print current_app.name
RuntimeError: working outside of application context
其实和request和session这两个请求上下文一样,应用上下文也只能在请求线程内使用。

(2)应用上下文环境构造

由于上篇已经有了介绍,这里直接贴Flask里的wsgi_app方法回顾:
class Flask(_PackageBoundObject):            #中间省略一些代码        def wsgi_app(self, environ, start_response):           ctx = self.request_context(environ)            ctx.push()            error = None            try:                #省略一些代码        finally:                #省略一些代码            ctx.auto_pop(error) 
同样关注这两行:
ctx = self.request_context(environ)    ctx.push() 
第一行是构建一个RequestContext实例赋给ctx。应用上下文的创建在ctx.push()方法里:
class RequestContext(object):      def push(self):          #省略一些代码          app_ctx = _app_ctx_stack.top          if app_ctx is None or app_ctx.app != self.app:              app_ctx = self.app.app_context()         #创建一个AppContext实例            app_ctx.push()              self._implicit_app_ctx_stack.append(app_ctx)          else:              self._implicit_app_ctx_stack.append(None)            #省略一些代码
self.app.app_context()方法和self.request_context()方法类似,return AppContext(self),那么可以查看AppContext类:
class AppContext(object):    def __init__(self, app):        self.app = app           #app=Flask(__name__)的实例        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
首先可以看到self.app =app,这个app是app = Flask(__naem__)创建的实例,也就是整个flask app。此外,看到self.g = app.app_ctx_globals_class(),将一个类赋给self.g。通过调试可以看到,self.g是<flask.ctx._AppCtxGlobals object at 0x7ffa7ef366d8>,定位到_AppCtxGlobals查看:
class _AppCtxGlobals(object):    """A plain object."""    def get(self, name, default=None):        return self.__dict__.get(name, default)    def pop(self, name, default=_sentinel):        if default is _sentinel:            return self.__dict__.pop(name)        else:            return self.__dict__.pop(name, default)    def setdefault(self, name, default=None):        return self.__dict__.setdefault(name, default)    def __contains__(self, item):        return item in self.__dict__    def __iter__(self):        return iter(self.__dict__)    def __repr__(self):        top = _app_ctx_stack.top        if top is not None:            return '<flask.g of %r>' % top.app.name        return object.__repr__(self)
由源码的注释可以看到这个_AppCtxGlobals是一个plain object ,意思是它将会有多个key/value对,同时,实现了一些方法。
现在,回到原来RequestContext类下的push方法:
class RequestContext(object):      def push(self):              app_ctx = self.app.app_context()         #创建一个AppContext实例            app_ctx.push()              self._implicit_app_ctx_stack.append(app_ctx)          else:              self._implicit_app_ctx_stack.append(None)            #省略一些代码
app_ctx得到这个AppContext实例,它自身有app和g两个属性,之后对它调用push()方法,并且压入_implicit_app_ctx_stack。
其实和request_ctx.push()效果类似,这里是把app_ctx压入_app_ctx_stack里。
当有了上篇的基础,也可以知道current_app和g都是LocalProxy实例,都有着__local属性,分别指向_find_app()和偏函数_lookup_app_object(g)。

(3)应用上下文的使用

使用current_app可以获取当前的app。而g的使用有点类似请求上下文中的session,用来临时保存一些信息或变量,例如g.user = current_user。这里会调用LocalProxy下的__setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)这个方法,先得到_app_ctx_stack的栈顶元素AppContext实例,然后获取它的g属性,其实就是那个_AppCtxGlobals实例,动态给它绑定一个user的属性,指向当前的用户。但与请求上下文中的session不同的是,g里临时保存的信息仅能在当前请求的整个生命周期内访问和使用,当请求处理完毕的时候,它将会被回收。换言之,每个请求之间g都要重设。

在请求处理结束后:
class Flask(_PackageBoundObject):            #中间省略一些代码        def wsgi_app(self, environ, start_response):           ctx = self.request_context(environ)            ctx.push()            error = None            try:                #省略一些代码        finally:                #省略一些代码            ctx.auto_pop(error) 
ctx.auto_pop()里对ctx调用了pop方法。查看源码:
class RequestContext(object):    def pop(self, exc=_sentinel):        app_ctx = self._implicit_app_ctx_stack.pop()        try:            #省略一些代码        finally:            rv = _request_ctx_stack.pop()            #省略一些代码            if app_ctx is not None:                app_ctx.pop(exc)
从self._implicit_app_ctx_stack取得栈顶元素赋给app_ctx,它同样也是_app_ctx_stack的栈顶元素,app_ctx.pop(exc),从_app_ctx_stack出栈。

请求上下文和应用上下文到这就基本介绍完毕了。

思考

就算了解了请求上下文和应用上下文,也会有很多疑惑。

(1)既然请求上下文和应用上下文生命周期都在线程内,其实他们的作用域基本一样,为什么还要两个级别的上下文存在呢?
(2)既然上下文环境只能在一个请求中,而一个请求中似乎也不会创建两个以上的请求或应用上下文。那用ThreadLocal本地变量就行,什么要用栈呢?

(3)为什么要放在“栈”里:在 Web 应用运行时中,一个线程同时只处理一个请求,那么 _req_ctx_stack 和 _app_ctx_stack 肯定都是只有一个栈顶元素的。那么为什么还要用“栈”这种结构?

查了一些资料后,对于第一个问题:虽然在flask应用中,一个app就能基本实现一个简单的web应用,我们知道对一个 Flask App 调用 app.run() 之后,进程就进入阻塞模式并开始监听请求。此时是不可能再让另一个 Flask App 在主线程运行起来的。那么还有哪些场景需要多个 Flask App 共存呢?前面提到了,一个 Flask App 实例就是一个 WSGI Application,那么 WSGI Middleware 是允许使用组合模式的,就可以支持多个app共存。就像request一样,在多app情况下也要保证app之间的隔离。那在flask中如何实现多个app呢?使用中间件DispatcherMiddleware。这个将在以后介绍。(挖坑了。。。)

对于第二第三个问题,其实回答是一样的。在web环境下,确实没必要弄这么麻烦,就算多个 Flask App 同时工作也不是问题,毕竟每个请求被处理的时候是身处不同的 Thread Local 中的。不过Flask支持在离线环境中跑自动测试。但是 Flask App 不一定仅仅在 Web Runtime 中被使用 —— 有两个典型的场景是在非 Web 环境需要访问上下文代码的,一个是离线脚本(前面提到过),另一个是测试。这两个场景即所谓的“Running code outside of a request”。这个将在以后介绍。(又挖坑。。。。。)

0 0
原创粉丝点击