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是通过上下文管理, 两种都可以实现,只不实现的方式不一样罢了. - 上 ...
随机推荐
- [Scala]Scala学习笔记一 基础
1. 变量 val定义的值实际上是一个常亮,无法改变其内容 scala> val num = 0 num: Int = 0 scala> num = 2 <console>:1 ...
- Linux SVN 切换用户
1. 临时切换 在所有命令前强制加上--username 和 --password 例如:svn up --username zhangsan --password 123456 2. 永久切 ...
- 10.Linux网卡的配置及详解
1.网卡配置文件在/etc/sysconfig/network-scripts/下: [root@oldboy network-scripts]# ls /etc/sysconfig/network- ...
- 从无到有开发自己的Wordpress博客主题---局部模板的准备
毫无疑问,我们媒体页面都会有header和footer,这些用到的内容几乎是一样的. 从无到有,我们先不考虑后面可能用到的Search和Comment等的模板,后面的我会在文本最后面追加. 开始之前, ...
- 你必须知道的495个C语言问题,学习体会二
这是本主题的第二篇文章,主要就结构体,枚举.联合体做一些解释 1.结构体 现代C语言编程 结构化的基石,diy时代的最好代言人,是面向对象编程中类的老祖宗. 我们很容易定义一个结构体,比如学生: st ...
- 演示使用Metasploit入侵Android
文本演示怎么使用Kali Linux入侵Android手机. Kali Linux IP地址:192.168.0.112:接收连接的端口:443. 同一局域网内android手机一部(android ...
- Ubuntu下安装为知笔记
之前在Windows下用的是有道云笔记,但是后来开始习惯使用Linux开发,有道云官方并没有提供Ubuntu的版本,所以权衡之下,选择了为知笔记,安装步骤: sudo add-apt-reposito ...
- CF632E: Thief in a Shop(快速幂+NTT)(存疑)
A thief made his way to a shop. As usual he has his lucky knapsack with him. The knapsack can contai ...
- iOS6 自动布局 入门–Auto Layout
目前为止,即使你的界面设计是在合理的复杂度内,你也必须要为之写许多代码来适应变化的布局.现在我相信你会很高兴听到这种情况将不会发生了-对于iPhone与iPad IOS6 带来了一个非常了不起的特征: ...
- Mapreduce shuffle和排序
Mapreduce为了确保每个reducer的输入都按键排序.系统执行排序的过程-----将map的输出作为输入传给reducer 称为shuffle.学习shuffle是如何工作的有助于我们理解ma ...