简析Flask上下文 / Context in Flask

Flask中的上下文是比较难以理解的一个点, 参考了大佬们的文章, 这里记录一下自己的理解, 仅供抛砖引玉.

何为上下文?

  • 上下文相当于一个容器, 保存了Flask程序运行过程中的一些信息. 如请求地址, Cookie等.
  • Flask中有两种上下文: 请求上下文(request context) 和 应用上下文(application context)
  • 应用上下文的生命周期依赖于请求的生命周期.

请求上下文(Request context)

请求上下文对象有:request、session

request

在 flask 中,可以直接在视图函数中使用 request 这个对象进行获取相关数据,而 request 就是请求上下文的对象,保存了当前本次请求的相关数据.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
from flask import Flask, g, request

app = Flask(__name__)


@app.before_first_request
def before_first_request():
print('before first request started')
print(request.url)


@app.before_request
def before_request():
print('before request started')
print(request.url)

g.name = "SampleApp"


@app.after_request
def after_request(response):
print('after request finished')
print(request.url)
response.headers['key'] = 'value' # 可在此方法中对响应做统一处理
return response


@app.teardown_request
def teardown_request(error):
print('teardown request')
print(request.url)


@app.route('/')
def index():
return 'Hello, %s!' % g.name


if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True)

访问localhost:5000后输出

1
2
3
4
5
6
7
8
before first request started
http://127.0.0.1:5000/
before request started
http://127.0.0.1:5000/
after request finished
http://127.0.0.1:5000/
teardown request
http://127.0.0.1:5000/

可以看出, 在每个请求上下文的勾子函数中, 我们都可以访问request对象, 并通过request.url得到其中存储的url信息.

如果我们在上述代码中加上一段:

1
2
3
4
5
def handle_request():
print('handle request')
print(request.url)

handle_request()

运行时会报错

1
2
RuntimeError: working outside of request context
# 超出请求上下文范围

由此可见, request对象仅在请求的生命周期中能被访问到, 一旦请求的生命周期结束, 其上下文环境就不复存在了.

通过request对象, 我们可以获得HTTP请求的内容, 比如: user = request.args.get(‘user’), 可以获取到get请求的参数.

session

session对象记录了请求会话中的用户信息.

1
2
3
4
5
6
7
8
9
10
11
from flask import Flask
from flask import session

# 为了设置session, 必须先配置secret_key
app.config['SECRET_KEY'] = '12345678'

@app.route('/login')
def login():
# 假装校验成功
session['user_name'] = 'laowang' # 可以记录用户信息
return 'success'

应用上下文(application context)

应用上下文的对象有: current_app , g对象

它的作用主要是帮助 request 获取当前的应用,它是伴 request 而生,随 request 而灭的。

current_app

应用程序上下文,用于存储应用程序中的变量,可以通过current_app.name打印当前app的名称,也可以在current_app中存储一些变量,例如:

  • 应用的启动脚本是哪个文件,启动时指定了哪些参数
  • 加载了哪些配置文件,导入了哪些配置
  • 连了哪个数据库
  • 有哪些public的工具类、常量
  • 应用跑再哪个机器上,IP多少,内存多大
1
2
3
4
5
6
7
current_app.name
current_app.test_value='value'

current_app.config.get("DEBUG") # True
# 如果SECRET_KEY在程序中以明文设置, 这样可以得到该值
password = current_app.config.get("SECRET_KEY")
print(password)

g变量

g 作为 flask 程序全局的一个临时变量,充当者中间媒介的作用,我们可以通过它传递一些数据,g 保存的是当前请求的全局变量,不同的请求会有不同的全局变量,通过不同的thread id区别

应用上下文的勾子函数

1
2
3
@app.teardown_appcontext
def teardown_db(error):
print('teardown application')

应用上下文也有压栈和出栈的操作。在请求线程创建时,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
2
3
4
5
6
7
8
# 代码摘选自flask 0.5 中的ctx.py文件, 进行了部分删减
class _RequestContext(object):

def __init__(self, app, environ):
self.app = app
self.request = app.request_class(environ)
self.session = app.open_session(self.request)
self.g = _RequestGlobals()

flask 使用_RequestContext的代码如下:

1
2
3
4
class Flask(object):

def request_context(self, environ):
return _RequestContext(self, environ)

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
2
3
4
request_class = Request # Request 是一个类,定义如下:

class Request(RequestBase):
...

所以:
self.request = app.request_class(environ)实际上是创建了一个Request对象。
由于,一个http请求对应一个_RequestContext对象的创建,而每个_RequestContext对象的创建对应一个Request对象的创建,所以,每个http请求对应一个Request对象。

到这里想必已经很清楚了:

application 就是指app = Flask(__name__)对象
request 就是对应每次http 请求创建的Request对象
flask通过_RequestContextappRequest关联起来

本文标题:简析Flask上下文 / Context in Flask

文章作者:Vincent Zheng

发布时间:2018年08月09日 - 10:08

最后更新:2018年08月16日 - 21:08

原始链接:https://zws910.github.io/2018/08/09/context-in-flask/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

0%