作者简介:大家好,我是爱敲代码的小王,CSDN博客博主,Python小白
系列专栏:python入门到实战、Python爬虫开发、Python办公自动化、Python数据分析、Python前后端开发
如果文章知识点有错误的地方,请指正!和大家一起学习,一起进步
如果感觉博主的文章还不错的话,请三连支持一下博主哦
博主正在努力完成2023计划中:以梦为马,扬帆起航,2023追梦人
python入门到实战专栏:从入门到实战
Python爬虫开发专栏:从入门到实战
Python办公自动化专栏:从入门到实战
Python数据分析专栏:从入门到实战
Python前后端开发专栏:从入门到实战
目录
Local对象
Flask_app上下文
Flask_request上下文详解
Flask_线程隔离的g对象
Flask_钩子函数介绍
需求
1、要实现并发效果, 每一个请求进来的时候我们都开启一个进程, 这显然是不合理的, 于是就可以使用线程2、那么线程中数据互相不隔离,存在修改数据的时候数据不安全的问题
Local对象
在Flask中,类似于 request 对象,其实是绑定到了一个 werkzeug.local.Local 对象上。
这样,即使是同一个对象,那么在多个线程中都是隔离的。类似的对象还有 session 对象。
from werkzeug.local import Local
#flask=werkzeug + sqlalchemy + jinja2
ThreadLocal变量
Python提供了ThreadLocal 变量,它本身是一个全局变量,但是每个线程却可以利用它来保存属于自己的私有数据,这些私有数据对其他线程也是不可见的。
from threading import Thread,local
local =local()
local.request = '具体用户的请求对象'
class MyThread(Thread):
def run(self):
local.request = 'txc'
print('子线程:',local.request)
mythread = MyThread()
mythread.start()
mythread.join()
print('主线程:',local.request)
from werkzeug.local import Local
local = Local()
local.request = '具体用户的请求对象'
class MyThread(Thread):
def run(self):
local.request = 'txc'
print('子线程:',local.request)
mythread = MyThread()
mythread.start()
mythread.join()
print('主线程:',local.request)
总结
只要满足绑定到"local"或"Local"对象上的属性,在每个线程中都是隔离的,那么他就叫做 ThreadLocal 对象,也叫'ThreadLocal'变量。
App上下文,也叫应用上下文
上下文(感性的理解)
每一段程序都有很多外部变量,只有像add这种简单的函数才是没有外部变量的。 一旦一段程序有了外部变量,这段程序就不完整,不能独立运行。为了能让这段程序可以运行,就要给所有的外部变量一个一个设置一些值。就些值所在的集合就是叫上下文。
并且上下文这一概念在中断任务的场景下具有重大意义,其中任务在被中断后,处理器保存上下文并提供中断处理,因些在这之后,任务可以在同一个地方继续执行。(上下文越小,延迟越小)举例
运行的Flask项目,每一个路由映射的内容片段,都不可以单独拿出来使用。当获取到了APP_Context以后,就可以直接通过程序映射的地址访问逻辑,并且可以重复使用。
上下文的一个典型应用场景就是用来缓存一些我们需要在发生请求之前或者要使用的资源。举个例子,比如数据库连接。当我们在应用上下文中来存储东西的时候你得选择一个唯一的名字,这是因为应用上下文为 Flask 应用和扩展所共享。
应用上下文:
应用上下文是存放到一个 LocalStack 的栈中。和应用app相关的操作就必须要用到应用上下文
比如:
通过 current_app 获取当前的这个 app 名字。
注意
在视图函数中,不用担心应用上下文的问题。因为视图函数要执行,那么肯定是通过访问url的方式执行的,那么这种情况下,Flask底层就已经自动的帮我们把应用上下文都推入到了相应的栈中。
注意
如果想要在视图函数外面执行相关的操作,比如: 获取当前的app名称,那么就必须要手动推入应用上下文
第一种方式:便于理解的写法
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 hello_world():
print(current_app.name) #获取应用的名称
return 'Hello World!'
if __name__ == '__main__':
app.run(debug=True)
第二种方式:用with语句
from flask import Flask,current_app
app = Flask(__name__)
#app上下文
#换一种写法
with app.app_context():
print(current_app.name)
@app.route('/')
def hello_world():
print(current_app.name) #获取应用的名称
return 'Hello World!'
if __name__ == '__main__':
app.run(debug=True)
请求上下文:
请求上下文也是存放到一个 LocalStack 的栈中。
和请求相关的操作就必须用到请求上下文,比如使用 url_for 反转视图函数。
注意
在视图函数中,不用担心请求上下文的问题。
因为视图函数要执行,那么肯定是通过访问url的方式执行的,
那么这种情况下,Flask底层就已经自动的帮我们把应用上下文
和请求上下文都推入到了相应的栈中。
注意
如果想要在视图函数外面执行相关的操作,
比如反转url,那么就必须要手动推入请求上下文:
底层代码执行说明:
1. 推入请求上下文到栈中,会首先判断有没有应用上下文
2. 如果没有那么就会先推入应用上下文到栈中
3. 然后再推入请求上下文到栈中
from flask import Flask,url_for
app = Flask(__name__)
@app.route('/')
def index():
url = url_for('test_url')
return f'Hello!==={url}'
@app.route('/test/')
def test_url():
return '这个是为了测试请求上下文'
# RuntimeError: Attempted to generate a URL
without the application context being
pushed.
# This has to be executed when application
context is available.
# with app.app_context():
# url = url_for('test_url')
# print(url)
# 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.test_request_context():
url = url_for('test_url')
print(url)
if __name__ == '__main__':
app.run(debug = True)
总结
为什么上下文需要放在栈中?
1. 应用上下文:Flask底层是基于werkzeug,werkzeug是可以包含多个app的,所以这时候用一个栈来保存。如果你在使用app1,那么app1应该是要在栈的顶部,如果用完了app1,那么app1应该从栈中删除。方便其他代码使用下面的app。
2. 如果在写测试代码,或者离线脚本的时候,我们有时候可能需要创建多个请求上下文,这时候就需要存放到一个栈中了。使用哪个请求上下文的时候,就把对应的请求上下文放到栈的顶部,用完了就要把这个请求上下文从栈中移除掉。
保存为全局对象g对象的好处:
g对象是在整个Flask应用运行期间都是可以使用的。并且也跟request一样,是线程隔离的。
这个对象是专门用来存储开发者自己定义的一些数据,方便在整个Flask程序中都可以使用。一般使用就是,将一些经常会用到的数据绑定到上面,以后就直接从g上面取就可以了,而不需要通过传参的形式,这样更加方便。
g对象使用场景:有一个工具类utils.py 和 用户办理业务:
def funa(uname):
print(f'funa {uname}')
def funb(uname):
print(f'funb {uname}')
def func(uname):
print(f'func {uname}')
用户办理业务
from flask import Flask,request
from utils import funa,funb,func
app = Flask(__name__)
#Flask_线程隔离的g对象使用详解
@app.route("/profile/")
def my_profile():
#从url中取参
uname = request.args.get('uname')
#调用功能函数办理业务
funa(uname)
funb(uname)
func(uname)
#每次都得传参 麻烦,引入g对象进行优化
return "办理业务成功"
if __name__ == '__main__':
app.run(debug=True)
优化工具类utils.py
from flask import g
def funa():
print(f'funa {g.uname}')
def funb():
print(f'funb {g.uname}')
def func():
print(f'func {g.uname}')
优化用户办理业务
from flask import Flask,request,g
from utils import funa,funb,func
app = Flask(__name__)
#Flask_线程隔离的g对象使用详解
@app.route("/profile/")
def my_profile():
#从url中取参
uname = request.args.get('uname')
#调用功能函数办理业务
# funa(uname)
# funb(uname)
# func(uname)
#每次都得传参 麻烦,引入g对象进行优化
g.uname = uname
funa()
funb()
func()
return "办理业务成功"
if __name__ == '__main__':
app.run(debug=True)
钩子函数概念
在Flask中钩子函数是使用特定的装饰器装饰的函数。为什么叫做钩子函数呢,是因为钩子函数可以在正常执行的代码中,插入一段自己想要执行的代码。那么这种函数就叫做钩子函数。
常见的钩子函数
before_first_request:处理项目的第一次请求之前执行。
@app.before_first_request
def first_request():
print('first time request')
before_request:在每次请求之前执行。通常可以用这个装饰器来给视图函数增加一些变量。请求已经到达了Flask,但是还没有进入到具体的视图函数之前调用。一般这个就是在视图函数之前,我们可以把一些后面需要用到的数据先处理好,方便视图函数使用。
@app.before_request
def before_request():
if not hasattr(g,'glo1'):
setattr(g,'glo1','想要设置的')
teardown_appcontext:不管是否有异常,注册的函数都会在每次请求之后执行。
@app.teardown_appcontext
def teardown(exc=None):
if exc is None:
db.session.commit()
else:
db.session.rollback()
db.session.remove()
template_filter:在使用Jinja2模板的时候自定义过滤器。
@app.template_filter("upper")
def upper_filter(s):
return s.upper()
context_processor:上下文处理器。使用这个钩子函数,必须返回一个字典。这个字典中的值在所有模版中都可以使用。这个钩子函数的函数是,如果一些在很多模版中都要用到的变量,那么就可以使用这个钩子函数来返回,而不用在每个视图函数中的 render_template 中去写,这样可以让代码更加简洁和好维护。
@app.context_processor
def context_processor():
if hasattr(g,'user'):
return {"current_user":g.user}
else:
return {}
errorhandler:errorhandler接收状态码,可以自定义返回这种状态码的响应的处理方法。在发生一些异常的时候,比如404错误,比如500错误,那么如果想要优雅的处理这些错误,就可以使用 errorhandler 来出来。
@app.errorhandler(404)
def page_not_found(error):
return 'This page does not exist',404