上下文:即语境,语意,在程序中可以理解为在代码执行到某一时刻时,根据之前代码所做的操作以及下文即将要执行的逻辑,可以决定在当前时刻下可以使用到的变量,或者可以完成的事情。维持一段程序正常运行的所需要的外部变量的值的集合,叫做上下文(context)
上下文的一个典型应用场景就是用来缓存一些我们需要在发生请求之前或者要使用的资源。举个例子,比如数据库连接。当我们在应用上下文中来存储东西的时候你得选择一个唯一的名字,这是因为应用上下文为 Flask 应用和扩展所共享。
Flask中有两种上下文:请求上下文和应用上下文
Flask中上下文对象:相当于一个容器,保存了 Flask 程序运行过程中的一些信息。
1、请求上下文(request context)
思考:在视图函数中,如何取到当前请求的相关数据?比如:请求地址,请求方式,cookie等等
在 flask 中,可以直接在视图函数中使用 request 这个对象进行获取相关数据,而 request 就是请求上下文的对象,保存了当前本次请求的相关数据,请求上下文对象有:request、session
- request
- 封装了HTTP请求的内容,针对的是http请求。举例:user = request.args.get('user'),获取的是get请求的参数。
- session
- 用来记录请求会话中的信息,针对的是用户信息。举例:session['name'] = user.id,可以记录用户信息。还可以通过session.get('name')获取用户信息。
请求上下文也是存放到一个 LocalStack 的栈中。
和请求相关的操作就必须用到请求上下文,比如使用 url_for 反转视图函数。
注意:
在视图函数中,不用担心请求上下文的问题。
因为视图函数要执行,那么肯定是通过访问url的方式执行的,
那么这种情况下,Flask底层就已经自动的帮我们把应用上下文和请求上下文都推入到了相应的栈中。
注意:
如果想要在视图函数外面执行相关的操作,比如反转url,那么就必须要手动推入请求上下文:
底层代码执行说明:
1. 推入请求上下文到栈中,会首先判断有没有应用上下文
2. 如果没有那么就会先推入应用上下文到栈中
3. 然后再推入请求上下文到栈中
为什么上下文需要放在栈中?
1. 应用上下文:Flask底层是基于werkzeug,werkzeug是可以包含多个app的,所以这时候用一个栈来保存。如果你在使用app1,那么app1应该是要在栈的顶部,如果用完了app1,那么app1应该从栈中删除。方便其他代码使用下面的app。
2. 如果在写测试代码,或者离线脚本的时候,我们有时候可能需要创建多个请求上下文,这时候就需要存放到一个栈中了。使用哪个请求上下文的时候,就把对应的请求上下文放到栈的顶部,用完了就要把这个请求上下文从栈中移除掉。
示例代码:
from flask import Flask, url_for
app = Flask(__name__)
@app.route('/')
def index():
url = url_for('request_url')
return f'Hello!==={url}'
@app.route('/test/')
def request_url():
return '这个是为了测试请求上下文'
# RuntimeError: Application was not able to create a URL adapter for request independent URL generation.
# You might be able to fix this by setting the SERVER_NAME config variable.
# with app.app_context():
# url = url_for('request_url')
# print(url)
with app.test_request_context():
url = url_for('request_url')
print(url)
if __name__ == '__main__':
app.run(debug=True)
运行结果:
2、应用上下文(application context)
它的字面意思是 应用上下文,但它不是一直存在的,它只是request context 中的一个对 app 的代理(人),所谓local proxy。它的作用主要是帮助 request 获取当前的应用,它是伴 request 而生,随 request 而灭的。
类型是LocalProxy,像全局变量一样工作,但只能在处理请求期间且在处理它的线程中访问,返回的栈顶元素不是应用上下文,而是flask的应用实例对象。
应用上下文的封装 = flask核心对象+和外部协作对象(在flask封装对象上再添加push、pop等)(请求上下文同理)
应用上下文是存放到一个 LocalStack 的栈中。和应用app相关的操作就必须要用到应用上下文。
应用上下文对象有:current_app,g
注意:
在视图函数中,不用担心应用上下文的问题。因为视图函数要执行,那么肯定是通过访问url的方式执行的,那么这种情况下,Flask底层就已经自动的帮我们把应用上下文都推入到了相应的栈中。
如果想要在视图函数外面执行相关的操作,比如: 获取当前的app名称,那么就必须要手动推入应用上下文
2.1 current_app
应用程序上下文,用于存储应用程序中的变量,可以通过current_app.name打印当前app的名称,也可以在current_app中存储一些变量,例如:
- 应用的启动脚本是哪个文件,启动时指定了哪些参数
- 加载了哪些配置文件,导入了哪些配置
- 连了哪个数据库
- 有哪些public的工具类、常量
- 应用跑再哪个机器上,IP多少,内存多大
示例代码1:
from flask import Flask, current_app
app = Flask(__name__)
# app上下文
app_context = app.app_context()
app_context.push()
print('程序启动时执行:', current_app.name)
@app.route('/')
def index():
print('接口请求访问时执行:', current_app.name) # 获取应用的名称
return 'Hello world!'
if __name__ == '__main__':
app.run(debug=True)
运行结果:
示例代码2: 【with用法】
from flask import Flask, current_app
app = Flask(__name__)
# app上下文, with用法
with app.app_context():
print('程序启动时执行:', current_app.name)
@app.route('/')
def index():
print('接口请求访问时执行:', current_app.name) # 获取应用的名称
return 'Hello world!'
if __name__ == '__main__':
app.run(debug=True)
运行结果:
示例代码:创建current_app_demo.py
from flask import Flask, current_app
app1 = Flask(__name__)
app2 = Flask(__name__)
# 以redis客户端对象为例
# 用字符串表示创建的redis客户端
# 为了方便在各个视图中使用,将创建的redis客户端对象保存到flask app中,
# 后续可以在视图中使用current_app.redis_cli获取
app1.redis_cli = 'app1 redis client'
app2.redis_cli = 'app2 redis client'
@app1.route('/route11')
def route11():
return current_app.redis_cli
@app1.route('/route12')
def route12():
return current_app.redis_cli
@app2.route('/route21')
def route21():
return current_app.redis_cli
@app2.route('/route22')
def route22():
return current_app.redis_cli
运行
export FLASK_APP=current_app_demo:app1
flask run
- 访问
/route11
显示app1 redis client
- 访问
/route12
显示app1 redis client
export FLASK_APP=current_app_demo:app2
flask run
- 访问
/route21
显示app2 redis client
- 访问
/route22
显示app2 redis client
作用
current_app
就是当前运行的flask app,在代码不方便直接操作flask的app对象时,可以操作current_app
就等价于操作flask app对象
2.2 g对象
g 作为 flask 程序全局的一个临时变量,充当中间媒介的作用,我们可以通过它在一次请求调用的多个函数间传递一些数据。每次请求都会重设这个变量。
g对象是线程隔离的。
保存为全局对象g对象的好处:
g对象是在整个Flask应用运行期间都是可以使用的。并且也跟request一样,是线程隔离的。
这个对象是专门用来存储开发者自己定义的一些数据,方便在整个Flask程序中都可以使用。
一般使用就是,将一些经常会用到的数据绑定到上面,以后就直接从g上面取就可以了,而不需要通过传参的形式,这样更加方便。
示例代码1:
from flask import Flask, g
app = Flask(__name__)
def db_query():
user_id = g.user_id
user_name = g.user_name
print('user_id={} user_name={}'.format(user_id, user_name))
@app.route('/user/')
def get_user_profile():
g.user_id = 123
g.user_name = 'dgw'
db_query()
return 'hello world'
@app.route('/index/')
def index():
db_query()
return '欢迎来到首页!'
if __name__ == '__main__':
app.run(debug=True)
运行结果:
2.3 g对象场景应用
有一个工具类utils.py 和 用户办理业务
示例代码1:
# main.py
from flask import Flask, request
from utils import func1, func2, func3
app = Flask(__name__)
# Flask_线程隔离的g对象使用
@app.route('/')
def index():
# 从url中取参数
name = request.args.get('name')
# 调用功能函数办理业务
func1(name)
func2(name)
func3(name)
return '办理业务成功'
if __name__ == '__main__':
app.run(debug=True)
# utils.py
def func1(name):
print(f'func1 name:{name}')
def func2(name):
print(f'func2 name:{name}')
def func3(name):
print(f'func3 name:{name}')
运行结果:
示例代码2:
# main.py
from flask import Flask, request, g
from utils import func1, func2, func3
app = Flask(__name__)
# Flask_线程隔离的g对象使用
@app.route('/')
def index():
# 从url中取参数
name = request.args.get('name')
# 调用功能函数办理业务
# 每次都得传参 麻烦,引入g对象进行优化
g.name = name
func1()
func2()
func3()
return '办理业务成功'
if __name__ == '__main__':
app.run(debug=True)
# utils.py
# 优化工具类
from flask import g
def func1():
print(f'func1 name:{g.name}')
def func2():
print(f'func2 name:{g.name}')
def func3():
print(f'func3 name:{g.name}')
运行结果:
2.4 g对象与请求钩子的综合案例
需求
- 构建认证机制
- 对于特定视图可以提供强制要求用户登录的限制
- 对于所有视图,无论是否强制要求用户登录,都可以在视图中尝试获取用户认证后的身份信息
实现
from flask import Flask, abort, g
app = Flask(__name__)
@app.before_request
def authentication():
"""
利用before_request请求钩子,在进入所有视图前先尝试判断用户身份
:return:
"""
# TODO 此处利用鉴权机制(如cookie、session、jwt等)鉴别用户身份信息
# if 已登录用户,用户有身份信息
g.user_id = 123
# else 未登录用户,用户无身份信息
# g.user_id = None
def login_required(func):
def wrapper(*args, **kwargs):
if g.user_id is not None:
return func(*args, **kwargs)
else:
abort(401)
return wrapper
@app.route('/')
def index():
return 'home page user_id={}'.format(g.user_id)
@app.route('/profile')
@login_required
def get_user_profile():
return 'user profile page user_id={}'.format(g.user_id)
app.run()
3、app_context 与 request_context
在Flask程序未运行的情况下,调试代码时需要使用current_app
、g
、request
这些对象,会不会有问题?该如何使用?
3.1 app_context
app_context
为我们提供了应用上下文环境,允许我们在外部使用应用上下文current_app
、g
可以通过with
语句进行使用
>>> from flask import Flask
>>> app = Flask('')
>>> app.redis_cli = 'redis client'
>>>
>>> from flask import current_app
>>> current_app.redis_cli # 错误,没有上下文环境
报错
>>> with app.app_context(): # 借助with语句使用app_context创建应用上下文
... print(current_app.redis_cli)
...
redis client
示例代码: 【使用with,对app_context()返回一个APPContext对象】
from flask import Flask, current_app
app = Flask(__name__)
print(app) # 输出结果:<Flask 'app'>
with app.app_context():
app2 = current_app
print(app2) # 输出结果:<Flask 'app'>
if __name__ == '__main__':
app.run()
3.2 request_context
request_context
为我们提供了请求上下文环境,允许我们在外部使用请求上下文request
、session
可以通过with语句进行使用
>>> from flask import Flask
>>> app = Flask('')
>>> request.args # 错误,没有上下文环境
报错
>>> environ = {'wsgi.version':(1,0), 'wsgi.input': '', 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/', 'SERVER_NAME': 'itcast server', 'wsgi.url_scheme': 'http', 'SERVER_PORT': '80'} # 模拟解析客户端请求之后的wsgi字典数据
>>> with app.request_context(environ): # 借助with语句使用request_context创建请求上下文
... print(request.path)
...
/