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

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

在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
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值