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.nameRuntimeError: 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”。这个将在以后介绍。(又挖坑。。。。。)
- Flask进阶(一)——请求上下文和应用上下文完全解答(下)
- Flask进阶(一)——请求上下文和应用上下文完全解答(上)
- Flask-应用(程序)上下文和请求上下文
- Flask: 程序和请求上下文
- 应用上下文&请求上下文
- Flask-请求上下文
- Flask源码解读 <2> --- 请求上下文和request对象
- 请求上下文
- Flask源码阅读(七)——上下文变量
- Flask系列教程(10)——上下文
- Flask 上下文处理器
- flask 上下文全局变量
- Flask的Context(上下文)
- Flask学习笔记---上下文
- Oracle Serucity — Oracle应用上下文
- 应用上下文
- 用flask开发个人博客(35)—— flask中的上下文处理器app_context_processor
- EF进阶篇(三)——上下文
- jq使用文本输入提示的方式验证贵美网站的注册
- Selenium-grid2 远程并发控制用例执行
- 持久化、DAO
- JSON和XML比较
- unit6~~文本处理工具
- Flask进阶(一)——请求上下文和应用上下文完全解答(下)
- forward内部跳转 和redirect重定向跳转的区别
- React/React Native 的ES5 ES6写法对照表
- 全面剖析【二叉树】的各类遍历方法
- 【京东商城首页实战9】导航菜单栏和下拉列表
- centos6.5下vim的配置
- 文本型数据的向量化:TF-IDF
- linux部署多个tomcat
- hive用户自定义函数