Flask 上下文(Context)原理解析
:first-child { margin-top: 0; } blockquote > :last-child { margin-bottom: 0; } img { border: 0; max-width: 100%; height: auto !important; margin: 2px 0; } table { border-collapse: collapse; border: 1px solid #bbbbbb; } td, th { padding: 4px 8px; border-collapse: collapse; border: 1px solid #bbbbbb; } @media only screen and (-webkit-max-device-width: 1024px), only screen and (-o-max-device-width: 1024px), only screen and (max-device-width: 1024px), only screen and (-webkit-min-device-pixel-ratio: 3), only screen and (-o-min-device-pixel-ratio: 3), only screen and (min-device-pixel-ratio: 3) { html, body { font-size: 17px; } body { line-height: 1.7; padding: 0.75rem 0.9375rem; color: #353c47; } h1 { font-size: 2.125rem; } h2 { font-size: 1.875rem; } h3 { font-size: 1.625rem; } h4 { font-size: 1.375rem; } h5 { font-size: 1.125rem; } h6 { color: inherit; } ul, ol { padding-left: 2.5rem; } blockquote { padding: 0 0.9375rem; } }
-->
div{font-size:15px;}.wiz-table-tools .wiz-table-menu-item.active .wiz-table-menu-sub {display: block}.wiz-table-tools .wiz-table-menu-sub:before, .wiz-table-tools .wiz-table-menu-sub:after {position: absolute;content: " ";border-style: solid;border-color: transparent;border-bottom-color: #cccccc;left: 22px;margin-left: -14px;top: -8px;border-width: 0 8px 8px 8px;z-index:10;}.wiz-table-tools .wiz-table-menu-sub:after {border-bottom-color: #ffffff;top: -7px;}.wiz-table-tools .wiz-table-menu-sub-item {padding: 4px 12px;font-size: 14px;}.wiz-table-tools .wiz-table-menu-sub-item.split {border-top: 1px solid #E0E0E0;}.wiz-table-tools .wiz-table-menu-sub-item:hover {background-color: #ececec;}.wiz-table-tools .wiz-table-menu-sub-item.disabled {color: #bbbbbb;cursor: default;}.wiz-table-tools .wiz-table-menu-sub-item.disabled:hover {background-color: transparent;}.wiz-table-tools .wiz-table-menu-item.wiz-table-cell-bg:hover .wiz-table-color-pad {display: block;}.wiz-table-tools .wiz-table-color-pad {display: none;padding: 10px;box-sizing: border-box;width: 85px;height: 88px;background-color: #fff;cursor: default;}.wiz-table-tools .wiz-table-color-pad > div{font-size:15px;}.wiz-table-tools .wiz-table-color-pad .wiz-table-color-pad-item {display: inline-block;width: 15px;height: 15px;margin-right: 9px;position: relative;}.wiz-table-tools .wiz-table-color-pad .wiz-table-color-pad-item i.pad-demo {position: absolute;top:3px;left:0;}.wiz-table-tools .wiz-table-color-pad .wiz-table-color-pad-item .icon-oblique_line{color: #cc0000;}.wiz-table-tools .wiz-table-color-pad .wiz-table-color-pad-item:last-child {margin-right: 0;}.wiz-table-tools .wiz-table-color-pad .wiz-table-color-pad-item.active i.editor-icon.icon-box {color: #448aff;}.wiz-table-tools .wiz-table-cell-align {display: none;padding: 10px;box-sizing: border-box;width: 85px;height: 65px;background-color: #fff;cursor: default;}.wiz-table-tools .wiz-table-cell-align .wiz-table-cell-align-item {display: inline-block;width: 15px;height: 15px;margin-right: 9px;position: relative;}.wiz-table-tools .wiz-table-cell-align .wiz-table-cell-align-item:last-child {margin-right:0}.wiz-table-tools .wiz-table-cell-align .wiz-table-cell-align-item i.valign{position: absolute;top:3px;left:0;color: #d2d2d2;}.wiz-table-tools .wiz-table-cell-align-item.active i.editor-icon.valign {color: #a1c4ff;}.wiz-table-tools .wiz-table-cell-align-item.active i.editor-icon.icon-box,.wiz-table-tools .wiz-table-cell-align-item.active i.editor-icon.align {color: #448aff;}.wiz-table-tools .wiz-table-color-pad .wiz-table-color-pad-item:last-child,.wiz-table-tools .wiz-table-cell-align .wiz-table-cell-align-item:last-child {margin-right: 0;}th.wiz-selected-cell-multi, td.wiz-selected-cell-multi {background: rgba(0,102,255,.05);}th:before,td:before,#wiz-table-col-line:before,#wiz-table-range-border_start_right:before,#wiz-table-range-border_range_right:before{content: " ";position: absolute;top: 0;bottom: 0;right: -5px;width: 9px;cursor: col-resize;background: transparent;z-index:100;}th:after,td:after,#wiz-table-row-line:before,#wiz-table-range-border_start_bottom:before,#wiz-table-range-border_range_bottom:before{content: " ";position: absolute;left: 0;right: 0;bottom: -5px;height: 9px;cursor: row-resize;background: transparent;z-index:100;}.wiz-table-container {}.wiz-table-body {position:relative;padding:0 0 10px;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;}.wiz-table-body table {margin:0;outline:none;}td,th {height:28px;word-break:break-all;box-sizing:border-box;outline:none;}body pre.prettyprint {padding:0;}body pre.prettyprint code {white-space: pre;}body pre.prettyprint.linenums {box-shadow:none; overflow: auto;-webkit-overflow-scrolling: touch;}body pre.prettyprint.linenums ol.linenums {box-shadow: 40px 0 0 #FBFBFC inset, 41px 0 0 #ECECF0 inset; padding: 10px 10px 10px 40px !important;}
-->
Flask提供了两种上下文,一种是应用上下文(Application Context),一种是请求上下文(Request Context)。其中 Application 表示用户响应 WSGI 请求的应用本身,即采用类似 app = Flask(name) 创建的对象。Request 则代表每次 HTTP 请求,其实质是 WSGI Server(例如 gunicorn)调用该 Flask.call() 后,于 Flask 对象内部创建的 Request 对象。
a.Thread Local 概念从面向对象设计的角度看,对象是保存“状态”的地方。Python 也是如此,一个对象的状态都被保存在对象携带的一个特殊字典中,可以通过 vars 函数拿到它。Thread Local 则是一种特殊的对象,它的“状态”对线程隔离,也就是说每个线程对一个 Thread Local 对象的修改都不会影响其他线程。这种对象的实现原理也非常简单,只要以线程的 ID 来保存多份状态字典即可,就像按照门牌号隔开的一格一格的信箱。在 Python 中获得一个这样的 Thread Local 最简单的方法是 threading.local(),如下即展示了 Thread Local 对象的相关特性。>>> import threading>>> storage = threading.local()>>> storage.foo = 1>>> print(storage.foo)1>>> class AnotherThread(threading.Thread):... def run(self):... storage.foo = 2... print(storage.foo) # 这这个线程里已经修改了>>>>>> another = AnotherThread()>>> another.start()2>>> print(storage.foo) # 但是在主线程里并没有修改1这样来说,只要能构造出 Thread Local 对象,就能够让同一个对象在多个线程下做到状态隔离。这个“线程”不一定要是系统线程,也可以是用户代码中的其他调度单元,例如 Greenlet(基于 Flask 的 Web 应用可以在 Gevent 或 Eventlet 异步网络库 patch 过的 Python 环境中正常工作。这二者都使用 Greenlet 而不是系统线程作为调度单元,而 Werkzeug 考虑到了这点,在 Greenlet 可用时用 Greenlet ID 代替线程 ID。)。b.Werkzeug 实现的 Local Stack 和 Local ProxyWerkzeug 没有直接使用 threading.local,而是自己实现了 werkzeug.local.Local 类。后者和前者有一些区别:(1).后者会在 Greenlet 可用的情况下优先使用 Greenlet 的 ID 而不是线程 ID 以支持 Gevent 或 Eventlet 的调度,前者只支持多线程调度;(2).后者实现了 Werkzeug 定义的协议方法 __release_local__,可以被 Werkzeug 自己的 release_pool 函数释放(析构)掉当前线程下的状态,前者没有这个能力。除 Local 外,Werkzeug 还实现了两种数据结构:LocalStack 和 LocalProxy。
①.LocalStackLocalStack 是用 Local 实现的栈结构,可以将对象推入、弹出,也可以快速拿到栈顶对象。当然,所有的修改都只在本线程可见。和 Local 一样,LocalStack 也同样实现了支持 release_pool 的接口。②.LocalProxyLocalProxy 则是一个典型的代理模式实现,它在构造时接受一个 callable 的参数(比如一个函数),这个参数被调用后的返回值本身应该是一个 Thread Local 对象。对一个 LocalProxy 对象的所有操作,包括属性访问、方法调用(当然方法调用就是属性访问)甚至是二元操作(Python 的对象方法是 Descriptior 实现的,所以方法就是一种属性;而 Python 的二元操作可以用双下划线开头和结尾的一系列协议,所以 foo + bar 等同于 foo.__add__(bar),本质还是属性访问。)都会转发到那个 callable 参数返回的 Thread Local 对象上。LocalProxy 的一个使用场景是 LocalStack 的 __call__ 方法。比如 my_local_stack 是一个 LocalStack 实例,那么 my_local_stack() 能返回一个 LocalProxy 对象,这个对象始终指向 my_local_stack 的栈顶元素。如果栈顶元素不存在,访问这个 LocalProxy 的时候会抛出 RuntimeError。
a.请求上下文(Request Context)在每个请求上下文的函数中我们都可以访问 Request 对象,然而 Request 对象却并不是全局的。例如创建一个包含 Request 对象的函数如下,当运行时则将报错 RuntimeError: working outside of request context。def handle_request():print 'handle request'print request.urlif __name__=='__main__':handle_request()
因此可知,Flask 的 Request 对象只有在其上下文的生命周期内才有效,离开了请求的生命周期,其上下文环境不存在了,也就无法获取 Request 对象了。可以使用 Flask 的内部方法 request_context() 来构建一个请求上下文,如下所示。from werkzeug.test import EnvironBuilderctx = app.request_context(EnvironBuilder('/','http://localhost/').get_environ())ctx.push()try:print request.urlfinally:ctx.pop()对于 Flask Web 应用来说,每个请求就是一个独立的线程。请求之间的信息要完全隔离,避免冲突。因此当每个请求进入的时候,都将请求上下文对象压栈入 Flask._request_ctx_stack 中。上下文压入栈后,再次请求的时候都是通过 _request_ctx_stack.top 在栈的顶端取出,所取到的永远是属于自己线程的对象,这样不同线程之间的上下文就做到了隔离。请求结束后,线程退出,Thread Local 本地变量也随即销毁,然后调用 ctx.pop() 弹出上下文对象并回收内存。b.应用上下文(Application Context)
注意:当 app = Flask(__name__) 构造出一个 Flask App 时,App Context 并不会被自动推入 Stack 中。所以此时 Local Stack 的栈顶是空的,current_app 也是 unbound 状态,如下例所示。>>> from flask import Flask>>> from flask.globals import _app_ctx_stack, _request_ctx_stack>>>>>> app = Flask(__name__)>>> _app_ctx_stack.top>>> _request_ctx_stack.top>>> _app_ctx_stack()<LocalProxy unbound>>>>>>> from flask import current_app>>> current_app<LocalProxy unbound>注意:倘若应用上下文使用不当,将导致程序无法正常运行。比如:编写一个离线脚本时,如果直接在一个 Flask-SQLAlchemy 写成的 Model 上调用 User.query.get(user_id),就会遇到 RuntimeError。因为此时 App Context 还没被推入栈中,而 Flask-SQLAlchemy 需要数据库连接信息时就会去取 current_app.config,current_app 指向的却是 _app_ctx_stack 为空的栈顶。解决的办法是运行脚本正文之前,先将 App 的 App Context 推入栈中,栈顶不为空后 current_app 这个 Local Proxy 对象就自然能将“取 config 属性” 的动作转发到当前 App 上了。如下例程序所示。>>> ctx = app.app_context()>>> ctx.push()>>> _app_ctx_stack.top<flask.ctx.AppContext object at 0x102eac7d0>>>> _app_ctx_stack.top is ctxTrue>>> current_app<Flask '__main__'>>>>>>> ctx.pop()>>> _app_ctx_stack.top>>> current_app<LocalProxy unbound>current_app 只能在请求线程里存在,因此它的生命周期也是在应用上下文里,离开了应用上下文也就无法使用。如下例所示,离开了应用上下文环境,current_app 的调用也会出现"RuntimeError: working outside of application context"。app = Flask('__name__')print current_app.name同样可以手动创建应用上下文:with app.app_context():print current_app.name注意:这里的 with 语句和 with open() as f 一样,可以为提供上下文环境省略简化一部分工作,这里就简化了其压栈和出栈操作。注意:当 Flask App 在作为 WSGI Application 运行时,会在每个请求进入的时候将请求上下文推入 _request_ctx_stack 中,而请求上下文一定是应用上下文之中,所以推入部分的逻辑有这样一条:如果发现 _app_ctx_stack 为空,则隐式地推入一个 App 上下文。这便是应用运行时不需要手动 app_context().push() 的原因。最终,在请求线程退出前,应用上下文将从其 Flask._app_ctx_stack 的栈中里弹出。
3.上下文(Context)设计理念
a.区分应用上下文及请求上下文,并采用堆栈保存上下文的原因。由于 App Context 和 Request Context 都是 Thread Local 的,所以区分应用及请求上下文的原因往往难以理解。同时,在 Web 应用运行时中,一个线程同时只处理一个请求,那么 _req_ctx_stack 和 _app_ctx_stack 肯定都是只有一个栈顶元素,所以起采用堆栈方式保存上下文同样存在疑问。实际上,Flask 是为了提供多个 Flask App 共存和非 Web Runtime 中灵活控制 Context 的可能性。
①.多个 Flask App 共存多数情况下,一个 Flask App 调用 app.run() 之后,进程就进入阻塞模式并开始监听请求。此时是不可能再让另一个 Flask App 在主线程运行起来的。但是 WSGI Middleware 是允许使用组合模式的,即使多个 App 同时运行,如下例程序所示。from werkzeug.wsgi import DispatcherMiddlewarefrom biubiu.app import create_appfrom biubiu.admin.app import create_app as create_admin_appapplication = DispatcherMiddleware(create_app(), {'/admin': create_admin_app()})上例就利用 Werkzeug 内置的 Middleware 将两个 Flask App 组合成一个 WSGI Application。这种情况下两个 App 都同时在运行,只是根据 URL 的不同而将请求分发到不同的 App 上处理。注意:这种用法和 Flask 的 Blueprint 是有区别的。Blueprint 虽然和这种用法很类似,但前者自己没有 App Context,只是同一个 Flask App 内部整理资源的一种方式,所以多个 Blueprint 可能共享了同一个 Flask App;后者面向的是所有 WSGI Application,而不仅仅是 Flask App,即使是把一个 Django App 和一个 Flask App 用这种用法整合起来也是可行的。②.非 Web Runtime 中灵活控制 ContextFlask App 不一定仅仅在 Web Runtime 中被使用,有两个典型的场景是在非 Web 环境需要访问上下文代码的,一个是离线脚本,另一个是测试,这两个场景即所谓的“Running code outside of a request”。例如:一个离线脚本需要操作两个 Flask App 关联的上下文,这时候栈结构的 App Context 优势就发挥出来了。from biubiu.app import create_appfrom biubiu.admin.app import create_app as create_admin_appapp = create_app()admin_app = create_admin_app()def copy_data():with app.app_context():data = read_data()with admin_app.app_context():write_data(data)mark_data_copied()例子中,无论有多少个 App,只要主动去 Push 它的 App Context,Context Stack 中就会累积起来。这样,栈顶永远是当前操作的 App Context。当一个 App Context 结束的时候,相应的栈顶元素也随之出栈。如果在执行过程中抛出了异常,对应的 App Context 中注册的 teardown 函数被传入带有异常信息的参数。
Flask 上下文(Context)原理解析的更多相关文章
- Flask的Context(上下文)学习笔记
上下文是一种属性的有序序列,为驻留在环境内的对象定义环境.在对象的激活过程中创建上下文,对象被配置为要求某些自动服务,如同步.事务.实时激活.安全性等等. 比如在计算机中,相对于进程而言,上下文就是进 ...
- Flask上下文管理、session原理和全局g对象
一.一些python的知识 1.偏函数 def add(x, y, z): print(x + y + z) # 原本的写法:x,y,z可以传任意数字 add(1,2,3) # 如果我要实现一个功能, ...
- Flask源码解析:Flask上下文
一.上下文(Context) 什么是上下文: 每一段程序都有很多外部变量.只有像Add这种简单的函数才是没有外部变量的.一旦你的一段程序有了外部变量,这段程序就不完整,不能独立运行.你为了使他们运行, ...
- 《Flask Web开发实战:入门、进阶与原理解析(李辉著 )》PDF+源代码
一句话评价: 这可能是市面上(包括国外出版的)你能找到最好的讲Flask的书了 下载:链接: https://pan.baidu.com/s/1ioEfLc7Hc15jFpC-DmEYBA 提取码: ...
- Flask 的 Context 机制
转自https://blog.tonyseek.com/post/the-context-mechanism-of-flask/ Flask 的 Context 机制 2014 年 07 月 21 日 ...
- Request 接收参数乱码原理解析一:服务器端解码原理
“Server.UrlDecode(Server.UrlEncode("北京")) == “北京””,先用UrlEncode编码然后用UrlDecode解码,这条语句永远为true ...
- flask上下文详解
一.前言 了解过flask的python开发者想必都知道flask中核心机制莫过于上下文管理,当然学习flask如果不了解其中的处理流程,可能在很多问题上不能得到解决,当然我在写本篇文章之前也看到了很 ...
- Flask上下文管理及源码刨析
基本流程概述 - 与django相比是两种不同的实现方式. - django/tornado是通过传参数形式实现 - 而flask是通过上下文管理, 两种都可以实现,只不实现的方式不一样罢了. - 上 ...
- flask 上下文管理 &源码剖析
基本流程概述 - 与django相比是两种不同的实现方式. - django/tornado是通过传参数形式实现 - 而flask是通过上下文管理, 两种都可以实现,只不实现的方式不一样罢了. - 上 ...
随机推荐
- (效果一)js实现上拉加载
实现思路:获取滚动元素的高度,滚动条距离顶部的距离,滚动条的高度, 算式:可视窗口的高度 + 滚动条距离顶部的距离 == 滚动条的高度就说明到底部. HTML <!doctype html> ...
- linux【基础命令】
最近在学linux,避免一些命令忘记,所以在此记录一下: linux文件列表遍历 ls -a 列出所有的文件及文件夹 包括隐藏的ls -l 列出文件目录的详细信息 history 查看历史命令ctrl ...
- volatile 续
上次的问题在看了一篇博客后有了点理解了 博文地址为 http://www.cnblogs.com/dolphin0520/p/3920373.html 按照文章中写的,在并发编程中,我们通常会遇到以下 ...
- 【解题报告】[动态规划]RQNOJ - PID72 / 拔河比赛
原题地址:http://www.rqnoj.cn/problem/72 解题思路:基本的01背包问题. 要求的就是在这些人中选出一些人,使得这些人的体重的和 不超过所有人的体重的一半 并最大. 代码: ...
- 理解javascript中的with关键字
说起js中的with关键字,很多小伙伴们的第一印象可能就是with关键字的作用在于改变作用域,然后最关键的一点是不推荐使用with关键字.听到不推荐with关键字后,我们很多人都会忽略掉with关键字 ...
- log4j及其log4j2的使用
简单的说 log4j2 是log4j2的升级版,据说采用了一些新技术(无锁异步.等等),使得日志的吞吐量.性能比log4j 1.x提高10倍,并解决了一些死锁的bug,而且配置更加简单灵活.其使用方式 ...
- sqlserver sql语句查看分区记录数、查看记录所在分区
select count(1) ,$PARTITION.WorkDatePFN(workdate) from imgfile group by $PARTITION.WorkDatePFN(workd ...
- Eclipse里git提交冲突rejected – non-fast-forward
Eclipse里commit代码,其实只是提交到本地仓库,需要push才会提交到远程的git仓库,这时是一个本地仓库到远程仓库的同步过程.Git是分布式的,每个人在本地仓库维护本地的自己的那一份代码, ...
- flyplane
看到别人的一个简单制作打飞机的demo,先保存下来有空可以研究一下: <!DOCTYPE html> <html lang="en"> <head&g ...
- OBS第三方推流直播教程
第三方推流使用场景 1.当使用YY客户端进行直播遇到问题,暂无解决方法的时候,可以使用第三方直播软件OBS进行推流. 2.对OBS情有独钟的主播. OBS简介: OBS是一款比较好用的开源直播软件,目 ...