上篇对请求上下文进行了详细解答。
在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)应用上下文环境构造
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
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对,同时,实现了一些方法。
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。
当有了上篇的基础,也可以知道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”。这个将在以后介绍。(又挖坑。。。。。)