flask中的上下文分两种,application context和request context,即应用上下文和请求上下文。
 
从名字上看,可能会有误解,认为应用上下文是一个应用的全局变量,所有请求都可以访问修改其中的内容;而请求上下文则是请求内可访问的内容。
但事实上,这两者并不是全局与局部的关系,它们都处于一个请求的局部中。
 
先说结论每个请求的g都是独立的,并且在整个请求内都是可访问修改的。
 
下面来研究一下。
 
上下文类的定义:
 
上下文类定义在flask.ctx模块中
 
class AppContext(object):
    def __init__(self, app):
        self.app = app
        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

 
查看了源代码,AppContext类即是应用上下文,可以看到里面只保存了几个变量,其中比较重要的有:
app是当前web应用对象的引用,如Flask;还有g,用来保存需要在每个请求中需要用到的请求内全局变量。
 
 
class RequestContext(object):
    def __init__(self, app, environ, request=None):
        self.app = app
        if request is None:
            request = app.request_class(environ)
        self.request = request
        self.url_adapter = app.create_url_adapter(self.request)
        self.flashes = None
        self.session = None

# Request contexts can be pushed multiple times and interleaved with
        # other request contexts.  Now only if the last level is popped we
        # get rid of them.  Additionally if an application context is missing
        # one is created implicitly so for each level we add this information
        self._implicit_app_ctx_stack = []

 
RequestContext即请求上下文,其中有我们熟悉的request和session,app和应用上下文中的app含义相同。
 
上下文对象的作用域
 
那么这两种上下文运行时是怎么被使用的呢?
 
线程有个叫做ThreadLocal的类,也就是通常实现线程隔离的类。而werkzeug自己实现了它的线程隔离类:werkzeug.local.Local。LocalStack就是用Local实现的。
 
在flask.globals模块中定义了两个LocalStack对象:
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
 
LocalStack是flask定义的线程隔离的栈存储对象,分别用来保存应用和请求上下文。
它是线程隔离的意思就是说,对于不同的线程,它们访问这两个对象看到的结果是不一样的、完全隔离的。这是根据pid的不同实现的,类似于门牌号。
 
而每个传给flask对象的请求,都是在不同的线程中处理,而且同一时刻每个线程只处理一个请求。所以对于每个请求来说,它们完全不用担心自己上下文中的数据被别的请求所修改。
 
然后就可以解释这个特性:从flask模块中引入的g、session、request、current_app是怎么做到同一个对象能在所有请求中使用并且不会冲突。
 
这几个对象还是定义在flask.globals中:
current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))
g = LocalProxy(partial(_lookup_app_object, 'g'))
 
LocalProxy类的构造函数接收一个callable参数,上面这几个就传入了一个偏函数。以g为例,当对g进行操作时,就会调用作为参数的偏函数,并把操作转发到偏函数返回的对象上。
 
查看这几个函数的实现:
def _lookup_req_object(name):
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError('working outside of request context')
    return getattr(top, name)

def _lookup_app_object(name):
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError('working outside of application context')
    return getattr(top, name)

def _find_app():
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError('working outside of application context')
    return top.app

 
由于_app_ctx_stack和_request_ctx_stack都是线程隔离的,所以对g的调用就是这样一个过程:
 
访问g-->从当前线程的应用上下文栈顶获取应用上下文-->取出其中的g对象-->进行操作。
所以可以通过一个g对象而让所有线程互不干扰的访问自己的g。
 
上下文对象的推送
 
构建Flask对象后并不会推送上下文,而在Flask对象调用run()作为WSGI 应用启动后,每当有请求进入时,在推送请求上下文前,如果有必要就会推送应用上下文。但运行了run就会阻塞程序,所以在shell中调试时,必须手动推送上下文;或者使用flask-scripts,它运行的任务会在开始时自动推送。
 
上面加粗的“如果有必要”,那么什么叫有必要呢?是不是意味着在每个线程里应用上下文只会被推送一次、一次请求结束下一次请求来的时候就不用再推送应用上下文了呢?
来看RequestContext的源码,push函数:
def push(self):    
    top = _request_ctx_stack.top
    if top is not None and top.preserved:
        top.pop(top._preserved_exc)

# Before we push the request context we have to ensure that there
    # is an application context.
    app_ctx = _app_ctx_stack.top
    if app_ctx is None or app_ctx.app != self.app:
        app_ctx = self.app.app_context()
        app_ctx.push()
        self._implicit_app_ctx_stack.append(app_ctx)
    else:
        self._implicit_app_ctx_stack.append(None)

_request_ctx_stack.push(self)

 
flask在推送请求上下文的时候调用push函数,他会检查当前线程的应用上下文栈顶是否有应用上下文;如果有,判断与请求上下文是否属于同一个应用。在单WSGI应用的程序中,后者的判断无意义。
此时,只要没有应用上下文就会推送一个当前应用的上下文,并且把该上下文记录下来。
 
请求处理结束,调用auto_pop函数,其中又调用自身的pop函数:
def pop(self, exc=None): 
   app_ctx = self._implicit_app_ctx_stack.pop()
         ………………      
      ………………
   rv = _request_ctx_stack.pop()   
    # Get rid of the app as well if necessary.
    if app_ctx is not None:
        app_ctx.pop(exc)
 
会把请求上下文和应用上下文都pop掉。
 
故,在单WSGI应用环境下,每个请求的两个上下文都是完全独立的(独立于线程上曾经的请求,独立于其他线程的请求)。Q.E.D
 
那么,什么时候没必要推送呢?事实上,每次请求到来的时候都会推送,都是有必要的。因为当Flask在作为WSGI应用运行的时候,不可能出现当前线程的应用上下文已存在的情况。
 
那么就要搞清什么时候会有已存在的应用上下文。
 
该博文在最后提到了“两个疑问”:①应用和请求上下文在运行时都是线程隔离的,为何要分开来?②每个线程同时只处理一个请求,上下文栈肯定只有一个对象,为何要用栈来存储?
博主认为,这两个设计都是为了在离线状态下调试用:
 
 
所以,综上所述,在非离线状态下,上下文栈在每个WSGI应用里是独立的,而每个应用里线程同时只处理一个请求,故上下文栈肯定只有一个对象。并且,在请求结束后都会释放,所以新的请求来的时候都会重新推送两个上下文。
 
小结:
解释了这么多,对于flask编程来说,只有一个应用上的结论:每个请求的g都是独立的,并且在整个请求内都是可访问修改的。
 
ps.本来只是想知道能否在请求中保存一个变量,就研究了g的生存周期和作用范围,最后花了5个小时左右读了flask英文文档、各种博文和源代码,写了这些文字。我这对于细枝末节的东西吹毛求疵的精神真是害苦了我。。。
 
附上一些其他的笔记:
 
全局变量g,会在每次请求到来时重置
flask.g

Just store on this whatever you want. For example a database connection or the user that is currently logged in.

 
Starting with Flask 0.10 this is stored on the application context and no longer on the request context which means it becomes available if only the application context is bound and not yet a request
这个意思是g在应用上下文,而不是请求上下文。只要push了应用上下文就可以使用g对象
不要误解为g是整个程序内共享的
 
 
The application context is created and destroyed as necessary. It never moves between threads and it will not be shared between requests.
 
推送程序上下文:app = Flask(xxx),     app.app_context().push() 推送了程序上下文,g可以使用,当前线程的current_app指向app

Flask上下文源码分析(一)的更多相关文章

  1. Flask上下文源码分析(二)

    前面第一篇主要记录了Flask框架,从http请求发起,到返回响应,发生在server和app直接的过程. 里面有说到,Flask框架有设计了两种上下文,即应用上下文和请求上下文 官方文档里是说先理解 ...

  2. Flask系列10-- Flask请求上下文源码分析

    总览 一.基础准备. 1. local类 对于一个类,实例化得到它的对象后,如果开启多个线程对它的属性进行操作,会发现数据时不安全的 import time from threading import ...

  3. Flask框架 (四)—— 请求上下文源码分析、g对象、第三方插件(flask_session、flask_script、wtforms)、信号

    Flask框架 (四)—— 请求上下文源码分析.g对象.第三方插件(flask_session.flask_script.wtforms).信号 目录 请求上下文源码分析.g对象.第三方插件(flas ...

  4. Flask请求和应用上下文源码分析

      flask的request和session设置方式比较新颖,如果没有这种方式,那么就只能通过参数的传递. flask是如何做的呢? 1:本地线程,保证即使是多个线程,自己的值也是互相隔离 1 im ...

  5. flask请求上下文源码分析

    一.什么是上下文 每一段程序都有很多外部变量,只有像add这种简单的函数才是没有外部变量的,一旦你的一段程序有了外部变量,这段程序就不完整了,不能独立运行,你为了使他们能运行,就要给所有的外部变量一个 ...

  6. flask WTForms源码分析及自定义WTForms

    首先我们来创建一个From类 from wtforms.form import Form from wtforms import StringField from wtforms.validators ...

  7. Flask 请求源码分析

    执行app.run()方法: def run(self, host=None, port=None, debug=None, **options): from werkzeug.serving imp ...

  8. Flask restful源码分析

    Flask restful的代码量不大,功能比较简单 参见 http://note.youdao.com/noteshare?id=4ef343068763a56a10a2ada59a019484

  9. Flask(4)- flask请求上下文源码解读、http聊天室单聊/群聊(基于gevent-websocket)

    一.flask请求上下文源码解读 通过上篇源码分析,我们知道了有请求发来的时候就执行了app(Flask的实例化对象)的__call__方法,而__call__方法返回了app的wsgi_app(en ...

随机推荐

  1. LCD驱动的学习

    简介: LCD是基于液晶的. LCD(liquid crystal display)按驱动方式分类可以分为静态驱动,简单矩阵驱动,主动矩阵驱动.其中,简单矩阵又可以分为扭转向列型(TN)和超转向列型( ...

  2. springboot 打包发布(war包)

    版本关系: 软件名称 版本号 软件名称 版本号 spring boot 2.x jdk 1.8 tomcat 9.x springboot中的pom.xml文件 打包:右键点击项目,选择如下图: 填写 ...

  3. python中read()、readline()、readlines()区别

    1.read([size])方法 read([size])方法从文件当前位置读取size个字节,若无参数size,则表示读取至文件结束位置,它范围为字符串对象   2.readline()方法 从字面 ...

  4. 静态库 VS 动态库

    如何得到库 ①先写好一堆的.c文件(.c:我们所需要的各种工具函数) ②将这些.c编译为对应的.o ③将所有的这些.o打包为一个仓库文件(静态库或者动态库) 静态库:按照静态库的方式打包 动态库:按照 ...

  5. pytorch版yolov3训练自己数据集

    目录 1. 环境搭建 2. 数据集构建 3. 训练模型 4. 测试模型 5. 评估模型 6. 可视化 7. 高级进阶-网络结构更改 1. 环境搭建 将github库download下来. git cl ...

  6. iptables详解(2)表中规则管理(增删改查)

    我们定义了四张表:raw表.mangle表.nat表.filter表,不同的表有不同的功能 filter表用来过滤,允许哪些ip.端口访问,禁止哪些ip.端口访问,表中会有很多链 ①禁止ip地址访问我 ...

  7. mysql官方下载安装教程(centos)

    Linux下Mysql 5.6.30 tar包安装 (2016-04-27 22:45:39) 转载▼ 环境:centos 6.4 x64 先下载mysql安装包 打开 http://dev.mysq ...

  8. STM32的指令周期

    在keil中编程时,写了一行代码,然后就想知道,执行这句C代码需要多长时间. 时钟周期在这就不解释了,频率的倒数. 指令周期,个人理解就是cpu执行一条汇编指令所需要的时间. 我们知道cm3使用的三级 ...

  9. 为什么Java那么火?

    承德SEO:常居编程语言榜首的 Java 已有 20 多年历史,它的实用性.性能和向后兼容性都无可替代,即使是忽略它的“年龄”也依然稳居第一 如今的 Java 几乎占据了C语言曾拥有的地位,而C语言在 ...

  10. javascript/Jquery 将字符串转换成变量名

    var a = ['a', 'b', 'c'] var obj = {} for(i = 0; i < a.length; i++){ obj[a[i]] = "abc" + ...