ThreadLocal变量
又名Local对象隔离线程间对象。
简介
在Flask中,类似于request对象,其实是绑定到了一个werkzeug.local.Local对象上。
这样,即使是同一个对象,那么在多个线程中都是隔离的。
只要满足绑定到"local"或"Local"对象上的属性,在每个线程中都是隔离的,那么他就叫做ThreadLocal对象,也叫ThreadLocal变量。
代码实现
ThreadLocal变量有两个实现方式(但是区别不是很明显):基本python代码法,threading中的local模块+Thread模块
flask框架,werkzeug.local中的Local模块
python基础代码
# 基础python代码
from threading import Thread,local
local = local()
local.request = '具体用户的请求要求'
class MyThread(Thread):
def run(self):
local.request = 'hahah'
print('子线程',local.request)
mythread = MyThread()
mythread.start()
mythread.join()
print('主线程',local.request)
flask框架中的threadlocal对象
# flask框架中提供的threadlocal对象的写法
from werkzeug.local import Local
local = Local()
local.request = '具体用户要求'
class Mythread(Thread):
def run(self):
local.request = '哈哈哈'
print('子线程',local.request)
mythread = Mythread()
mythread.start()
mythread.join()
print('主线程',local.request)
app上下文(应用上下文)的底层原理
定义
应用上下文(app上下文)是存放到一个LocalStack的栈中。遵循先进后出的原则,那么appContext一定是放在最上边的,这样才能立马取出,并且应用app相关操作就必须要使用应用上下文
以current_app获取app名字为例
原理首先在栈内存中创建一个LocalStack。
通过current_app在Localstack中创建一个appcontext,使用的时候把APPcontext取出就OK了。
问题所在在于创建appcontext,而且appcontext如何放在top。
视图函数内
其实不用担心,因为视图函数一执行,一定是访问URL的方式下执行的,在这种情况下,FLask底层已经自动帮我们把应用上下文推入相应的栈中。
视图函数外
如果想要在视图函数外面执行相关的操作,比如获取当前的app名称,那么就需要手动推入应用上下文。底层原理:
要在视图函数外实现功能,就应该看一下current_app底层什么?发现为LocalProxy(_find_app),到这已经可以知道current_app功能是基于local隔离线程间对象的功能的。
那么find_app又是什么呢?一个判断函数,若top没有就返回异常app_ctx_err_msg也就是之前看到的RuntimeError。
需要给localStack添加top项appcontext.
方法一:容易理解
# 如果想要在视图函数外面执行相关的操作,比如获取当前的app名称,那么就必须要手动推入应用上下文
# 手动推入应用上下文方式一:
app_context_obj = app.app_context() # 手动创建应用上下文对象
app_context_obj.push() # 手动将应用上下文对象推入栈内存的顶端
print(current_app.name) # 获取应用上下文的名字
方法二:简便
# 手动推入应用上下文方式2
with app.app_context():
print(current_app.name) # 获取应用上下文的名字
request上下文(请求上下文)
定义
请求上下文也是存放到一个LocalStack的栈中。和请求相关的操作就必须用到请求上下文,比如使用url_for反转视图函数。
使用url_for反转视图函数
视图函数内
在视图函数中,不用担心请求上下文的问题。因为视图函数要执行,那么肯定是通过访问url的方式执行的,
那么这种情况下,Flask底层就已经自动的帮我们把应用上下文和请求上下文都推入到了相应的栈中。顺序是先把应用上下文推入栈中,再把请求上下文推入栈中。
视图函数外
底层代码执行说明:
* 推入请求上下文到栈中,会首先判断有没有应用上下文,
* 如果没有那么就会先推入应用上下文到栈中,
* 然后再推入请求上下文到栈中
from flask import Flask,url_for
app = Flask(__name__)
#request上下文
@app.route('/hi')
def hi():
print(url_for('my_list')) #获取构建得到的url
return 'Hello World!'
@app.route('/list/')
def my_list():
return '返回列表'
# print(url_for('my_list')) #获取构建得到的url
# with app.app_context():
# print(url_for('my_list'))
#查看报错源码
with app.test_request_context():
#手动推入一个请求上下文到请求上下文栈中
#如果当前app应用上下文栈中没有app应用上下文
#那么会首先推入一个app应用上下文到栈中
print(url_for('my_list'))
if __name__ == '__main__':
app.run(debug=True)
总结:为什么上下文要放在栈中?.应用上下文:Flask底层是基于werkzeug,werkzeug是可以包含多个app的,相当于这个Falsk项目全部一起运行,所以这时候用一个栈来保存。如果你在使用app1,那么app1应该是要在栈的顶部,如果用完了app1,那么app1应该从栈中删除,方便其他代码使用下面的app。
如果在写测试代码,或者离线脚本的时候,我们有时候可能需要创建多个请求上下文,这时候就需要存放到一个栈中了。使用哪个请求上下文的时候,就把对应的请求上下文放到栈的顶部,用完了就要把这个请求上下文从栈中移除掉。
g对象
使用g对象的好处g对象是在整个Flask应用运行期间都是可以使用的。
并且也跟request一样,是线程隔离的。
这个对象是专门用来存储开发者自己定义的一些数据,方便在整个Flask程序中都可以使用。
一般使用就是,将一些经常会用到的数据绑定到上面,以后就直接从g上面取就可以了,而不需要通过传参的形式,这样更加方便。
g对象应用举例
举例:一个工具类utils.py 实现用户办理业务。
工具类
# def funa(uname):
# print('funa %s'%uname)
# def funb(uname):
# print('funb %s'%uname)
# def func(uname):
# print('func %s'%uname)
# 优化之后
from flask import g
def funa():
print('funa%s'%g.uname)
def funb():
print('funa%s'%g.uname)
def func():
print('funa%s' % g.uname)
主Py文件
# 有一个工具类utils.py 和 用户办理业务:
@app.route('/profile/')
def my_profile():
# 从url中取参数
uname = request.args.get('uname')
# 调用功能函数办理业务
# funa(uname)
# funb(uname)
# func(uname)
g.uname = uname
funa()
funb()
func()
# 每次传参数,太麻烦了,引用g对象进行优化
# 办理业务成功
return '办理业务成功'
钩子函数概念:在Flask中钩子函数是使用特定的装饰器装饰的函数。在正常执行的代码中,插入一段自己想要的代码。
常见的钩子函数before_first_request:处理项目的第一次请求之前执行(只是第一次)
before_request:每次请求之前执行
通常可以用这个装饰器来给视图函数增加一些变量。请求已经到达了Flask,但是还没有进入到具体的视图函数之前调用。一般这个就是在视图函数之前,我们可以把一些后面需要用到的数据先处理好,方便视图函数使用。
teardown_appcontext:异常处理
不管是否有异常,注册的函数 都会在每次请求之后执行。
template_filter:自定义过滤器时使用
context_processor:上下文处理器
使用这个钩子函数,必须返回一个字典。这个字典中的值在所有模版中都可以使用。
errorhandler :接收状态码
可以自定义返回这种状态码的响应的处理方法。在发生一些异常的时候,比如404错误,比如500错误,那么如果想要优雅的处理这些错误,就可以使用errorhandler来出来。
详细使用钩子函数
before_first_request
# 当项目部署好以后,第一次请求之前执行某些特殊操作
# 1.before_first_request:处理项目的第一次请求之前执行。
@app.before_first_request
def first_request():
print('我想在第一次请求之前执行')
before_request每次请求之前都执行
场景:若用户已经登录了,验证时把用户名放入session中,之后取出来,放入钩子函数,以后访问的视图函数中可直接取出来使用。
from flask import Flask,session,g
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)@app.route('/')
def hello_world():
print('hello')
session['uname'] = 'MGorz'
return 'Hello World!'
@app.route('/li/')
def my_list():
print('mylist')
if hasattr(g,'user'):
print('条件取出',g.user)
return 'hello world'
@app.before_request
def before_request():
print('在视图函数执行之前执行钩子函数')
uname = session.get('uname')
print(uname)
if uname :
g.user = uname
context_processor上下文处理器,必须返回一个字典。
@app.context_processor
def context_processor():
if hasattr(g,'user'):
return {"current_user":g.user}
else:
return {}