Flask中的上下文是比较难以理解的一个点, 参考了大佬们的文章, 这里记录一下自己的理解, 仅供抛砖引玉.
何为上下文?
- 上下文相当于一个容器, 保存了Flask程序运行过程中的一些信息. 如请求地址, Cookie等.
- Flask中有两种上下文: 请求上下文(request context) 和 应用上下文(application context)
- 应用上下文的生命周期依赖于请求的生命周期.
请求上下文(Request context)
请求上下文对象有:request、session
request
在 flask 中,可以直接在视图函数中使用 request 这个对象进行获取相关数据,而 request 就是请求上下文的对象,保存了当前本次请求的相关数据.
1 | from flask import Flask, g, request |
访问localhost:5000后输出
1 | before first request started |
可以看出, 在每个请求上下文的勾子函数中, 我们都可以访问request对象, 并通过request.url得到其中存储的url信息.
如果我们在上述代码中加上一段:
1 | def handle_request(): |
运行时会报错
1 | RuntimeError: working outside of request context |
由此可见, request对象仅在请求的生命周期中能被访问到, 一旦请求的生命周期结束, 其上下文环境就不复存在了.
通过request对象, 我们可以获得HTTP请求的内容, 比如: user = request.args.get(‘user’), 可以获取到get请求的参数.
session
session对象记录了请求会话中的用户信息.
1 | from flask import Flask |
应用上下文(application context)
应用上下文的对象有: current_app , g对象
它的作用主要是帮助 request 获取当前的应用,它是伴 request 而生,随 request 而灭的。
current_app
应用程序上下文,用于存储应用程序中的变量,可以通过current_app.name打印当前app的名称,也可以在current_app中存储一些变量,例如:
- 应用的启动脚本是哪个文件,启动时指定了哪些参数
- 加载了哪些配置文件,导入了哪些配置
- 连了哪个数据库
- 有哪些public的工具类、常量
- 应用跑再哪个机器上,IP多少,内存多大
1 | current_app.name |
g变量
g 作为 flask 程序全局的一个临时变量,充当者中间媒介的作用,我们可以通过它传递一些数据,g 保存的是当前请求的全局变量,不同的请求会有不同的全局变量,通过不同的thread id区别
应用上下文的勾子函数
1 |
|
应用上下文也有压栈和出栈的操作。在请求线程创建时,Flask会创建应用上下文对象,并将其压入”flask._app_ctx_stack”的栈中,然后在线程退出前将其从栈里弹出。这个”_app_ctx_stack”是ThreadLocal变量。也就是说应用上下文的生命周期,也只在一个请求线程内,我们无法通过应用上下文在请求之间传递信息。
“_app_ctx_stack”一样是给Flask扩展开发用,应用开发不要去访问它。如果想在应用上下文中保存信息,可以用”flask.g”对象。
应用上下文只有一个装饰器来修饰Hook函数,即”@app.teardown_appcontext”。 它会在应用上下文生命周期结束前,也就是从”_app_ctx_stack”出栈时被调用。
Flask如何实现上下文?
下面通过源码了解一下 flask 如何实现这两种context:
1 | # 代码摘选自flask 0.5 中的ctx.py文件, 进行了部分删减 |
flask 使用_RequestContext
的代码如下:
1 | class Flask(object): |
在Flask
类中,每次请求都会调用这个request_context
函数。这个函数则会创建一个_RequestContext
对象。
值得注意的是:这个对象在创建时,将Flask
实例的本身作为实参传入_RequestContext
自身,因此,self.app = Flask()
。
所以,虽然每次http请求都会创建一个_RequestContext
对象,但是,每次创建的时候都会将同一个Flask
对象传入该对象的app
成员变量,使得:
由同一个
Flask
对象响应的请求所创建的_RequestContext
对象的app
成员变量都共享同一个application
通过在Flask
对象中创建_RequestContext
对象,并将Flask
自身作为参数传入_RequestContext
对象的方式,实现了多个request context对应一个application context 的目的。
接下来,看self.request = app.request_class(environ)
这句。
由于app
成员变量就是app = Flask(__name__)
这个对象,所以,app.request_class
就是Flask.request_class
。
在Flask
类的定义中:
1 | request_class = Request # Request 是一个类,定义如下: |
所以:self.request = app.request_class(environ)
实际上是创建了一个Request
对象。
由于,一个http请求对应一个_RequestContext
对象的创建,而每个_RequestContext
对象的创建对应一个Request
对象的创建,所以,每个http请求对应一个Request
对象。
到这里想必已经很清楚了:
application 就是指app = Flask(__name__)
对象
request 就是对应每次http 请求创建的Request
对象
flask通过_RequestContext
将app
与Request
关联起来