Web后端学习笔记 Flask(11)Local线程隔离对象
flask中的上下文:应用上下文和请求上下文
1. 在flask中,是通过request对象获取用户提交的数据,但是在整个程序运行中,只有一个request对象。在实际应用场景中,会有多个用户同时进行数据提交。此时应该开多个子线程,或者协程进行处理(即有多个request独立对象)。在Flask中通过Local解决这一问题。
只要绑定在Local对象上的属性,在每个线程中都是隔离的
local对象的原理:在local对象中,有一个字典,字典中存储的是,线程的id, 以及用户的请求内容。在多线程中,如果需要访问request中的值,因为此时的request对象是绑定在local对象上的,因此local对象会根据当前代码在哪一个线程下运行的,找到线程的id,以及对应线程下面request的内容,再将这邪恶内容放入到request对象中。
例如:
在主线程中的变量,如果不隔离,那么在子线程中如果改变了它的值,主线程中的值也就被修改了:
# -*- coding: utf-8 -*- from threading import Thread request = 123 class MyThread(Thread): def run(self): global request request = "abc" print("子线程", request) t1 = MyThread() t1.start() t1.join() print("主线程", request)
运行结果:
可以看到,主线程中变量request的值,在子线程中被修改,此时变量的值无论是在子线程还是主线程中都发生了变化。
# -*- coding: utf-8 -*- from threading import Thread from werkzeug.local import Local local = Local() local.request = 123 class MyThread(Thread): def run(self): local.request = "abc" print("子线程", local.request) t1 = MyThread() t1.start() t1.join() print("主线程", local.request)
将request变量绑定到local对象上,此时在不同的线程中,request变量实现了隔离,值相互不影响。
session对象也是绑定在Local对象上,所以它也是线程隔离的:
app上下文和request上下文:
app上下文:
@app.route('/') def hello_world(): print(current_app.name) return 'Hello World!'
在flask中,通过url访问视图函数的时候,flask会自动生成一个app上下文(app_context),然后将app上下文push到一个称为LocalStack()的栈中,而current_app相当于一个指针,始终指向LocalStack()的栈顶元素。
可以看到,current_app获取LocalStack()栈顶元素的过程
所以当我们在视图函数的外面访问current_app的时候,就会报错,原因是因为此时还没有通过url访问视图函数,LocakStack()中还没有压入任何的app_context,所以此时的current_app指向的是一个空的元素。
在视图函数外面访问app, flask不会动的将app_context压入堆栈,需要先手动创建app上下文,然后手动将其压入LocalStack()栈,才能够通过current_app进行访问。
也可以使用with语句创建app_context,更加的方便
request上下文:
在视图函数中,可以通过url_for("视图函数")来对视图函数进行url反转,但是在视图函数之外,如何对视图函数进行url反转?在视图函数之外需要手动创建一个请求上下文,因为在url_for方法开始的地方,需要获取到app_context 和request_context
from flask import Flask, request, current_app, url_for from werkzeug.local import Local import config app = Flask(__name__) app.config.from_object(config) @app.route('/') def hello_world(): print(current_app.name) print(url_for("my_list")) # 视图函数内反转 return 'Hello World!' @app.route('/list/') def my_list(): return "my list" with app.test_request_context(): # 手动推入一个请求上下文到上下文栈中 # 如果当前应用上下文栈中没有引用上下文 # 那么首先推入一个应用上下文到栈中 print(url_for("my_list")) if __name__ == '__main__': app.run()
应用上下文和请求上下文都存放到一个LocalStack栈中,和应用app相关的操作就必须用到应用上下文。比如通过current_app获取当前的app.和请求相关的操作就必须使用到请求上下文,例如利用url_for反转视图函数。
1. 在视图函数中,不用担心上下文的问题,因为视图函数要执行,那么一定是通过访问url的方式进行的,此时flask底层已经自动将请求上下文和应用上下文推入到了LocalStack栈中。
2. 如果在视图函数外面需要执行相关的操作,比如获取当前的app或者反转url,那么就必须手动推入相关的上下文。在flask中,可以使用with语句简洁的实现。
为什么上下文需要放在栈中:
1. 应用上下文:Flask底层是基于werkzeug,werkzeug是可以包含多个app,所以用一个栈保存,使用某个app,则这个app应该处于栈顶,app使用完毕,应该从栈中删除。
2. 请求上下文:在写测试代码,或者离线脚本的时候,可能需要创建多个请求上下文,这时候就需要存放到一个栈中。使用哪个请求上下文,就把该请求上下文放到栈顶,用完后从栈中删除。
线程隔离的g对象:
g对象,global的简写。g对象是在整个flask应用运行期间都是可以使用的,并且也跟request一样,是线程隔离的。这个对象是专门用来存储开发者自己定义的一些数据,方便在整个Flask程序中都可以使用。可以将一些常用的数据绑定到上面,以后再使用的时候就可以直接从g上面获取,而不需要通过传参的形式,这样更加方便。
例如:
在common_tools.py中定义一些公共的方法:
# -*- coding: utf-8 -*- from flask import g def log_info(): print("This device {} is working".format(g.device_id)) def log_error(): print("This device {} is not working".format(g.device_id))
在需要调用这些函数的地方,可以用g对象存储需要的数据,而不需要再通过传参的方式进行:
from flask import Flask, request, current_app, url_for, g import config from common_tools import log_info, log_error app = Flask(__name__) app.config.from_object(config) @app.route('/') def hello_world(): g.device_id = "#COD_001" log_info() log_error() return 'Hello World!' if __name__ == '__main__': app.run()
flask钩子函数:
Flask中钩子函数时使用特定装饰器的函数,为什么叫钩子函数,是因为钩子函数可以在正常执行的代码中,插入一段自己想要执行的代码,那么这种函数就叫做钩子函数。(hook function)
常用的钩子函数:
1. before_first_request: 在flask项目部署之后,第一次请求之前,会调用这个钩子函数,其他情况下不会再调用
@app.before_first_request # 在请求之前,会先执行这个钩子函数 def first_request(): print("first request")
2. before_request:在请求发生后,还没有执行视图函数之前,都会先执行这个函数。
@app.before_request def before_request_f(): # 例如在视图函数执行之前,如果这个用户是登陆状态的 # 可以把跟用户相关的一些信息绑定到g对象上,然后到具体的视图函数中, # 就可以使用,g对象中的数据 user_id = session.get("user_id") user_nickname = session.get("user_nickname") if user_id: g.user_id = user_id # 存储用户信息到g对象中 g.user_nickname = user_nickname print("first request")
这样做的好处,如果需要在多个视图函数中都需要用到用户的信息,只需要在相应的视图函数中调用g对象即可。如果将获取用户的信息的代码放在试图函数中,那么每一个需要用到这些信息的视图函数,都需要编写一段获取用户信息的代码。
3. template_filter: 在使用jinja2模板的时候,自定义过滤器。
@app.template_filter def upper_filter(s): return s.upper()
4. context_processor, 上下文处理器,在钩子函数中返回的值,在所有模板中都会使用到,且上下文处理器中必须返回字典。例如,在一般需要登陆的网页中,如果处于登陆状态,即使在不同页面之间相互跳转,也会在所有页面上显示用户名。
例如,在两个页面,index和list中,都需要用到用户名:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <p>index页面用户名:{{ current_user }}</p> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <p>List页面用户名:{{ current_user }}</p> </body> </html>
那么可以在钩子函数,上下文处理器中返回这个变量的值:此时所有的页面都可以使用,而不用在向render_template传递参数。
from flask import Flask, render_template import config app = Flask(__name__) app.config.from_object(config) @app.route('/') def index(): return render_template("html/index.html") @app.route('/list/') def my_list(): return render_template("html/list.html") @app.context_processor def context_process(): return {"current_user": "tom"} # 必须返回字典 if __name__ == '__main__': app.run()
5. errorhandler: 接受状态码,可以自定义返回状态码的相应处理方法。再服务端程序发生异常的时候,比如404,500错误,那么如果想要优雅的处理这些错误,就可以使用errorhandler来完成,例如:
@app.errorhandler(500) # 服务器内部错误 def server_error(error): print(error) return "刷新不要太频繁", 500 @app.errorhandler(404) def page_not_exist(error): print(error) return "页面不存在了", 404 # 字符串也可以替换为render_template
6. abort(status_code) 可以在flask程序中的任何地方使用,相当于手动抛出一个错误
例如在用户登陆的时候,用户不存在,可以在视图函数中经过判断之后手动abort(400),然后再定义errorhandler(400)的钩子函数来处理这种错误。
Flask中信号机制及使用场景:
flask中的信号使用的是一个第三方插件,blinker,通过pip install 安装即可,一般是跟随flask同时安装的。
自定义信号,分为三步:
1. 定义信号:定义信号需要使用到blinker下的Namespace类来创建一个命名空间。比如,定义一个在访问了某个视图函数的时候的信号:需要将自己创建的信号放到命名空间中:
# 定义信号 cx_space = Namespace() # 创建命名空间 cx_signal = cx_space.signal(name="greet") # 定义信号
2. 监听信号:监听信号使用signal对象的connect方法,在这个方法中需要传递一个函数,用来做监听到信号以后的操作。
# 监听信号 def greet_func(sender): """ :param sender: 这个参数必须写,表示信号的发送者 :return: """ print(sender) print("hello_fore") cx_signal.connect(greet_func)
3. 发送信号:发送信号使用signal对象的send方法,这个方法可以传递一些参数过去
# 发送信号 cx_signal.send()
实际应用场景:
定义一个登陆信号,在用户登陆进来以后,就发送一个登陆信号,然后开始监听这个信号,在监听到这个信号之后,就开始记录当前用户的信息,即用信号的方式,记录用户的登陆信息。
# -*- coding: utf-8 -*- from blinker import Namespace namespace = Namespace() login_signal = namespace.signal(name="login") def login_log(sender): print(sender) print("用户已经登陆") login_signal.connect(login_log) # 监听信号
在视图函数中发送信号:
@app.route('/login/') def login(): username = request.args.get("username") # 通过查询字符串的方式获取参数 if username: login_signal.send() return "success login {}".format(username) else: return "Please Input Username"
对于信号中参数的传递,有两个方案
a.在发送信号的时候,也可以发送参数过去
@app.route('/login/') def login(): username = request.args.get("username") # 通过查询字符串的方式获取参数 if username: login_signal.send(username=username) return "success login {}".format(username) else: return "Please Input Username"
def login_log(sender, username): now = datetime.now() ip = request.remote_addr # 获取IP地址 log_line = "{}|{}|{}".format(username, now, ip) with open("log/log.txt", "a") as fp: fp.write(log_line + "\n") print("用户已经登陆")
b.将参数在登录之后,放入到g对象中,然后发送信号,记录日志,记录日志的函数直接在g对象中调取用户登录信息。
Flask中的内置信号:
1. template_rendered: 模板渲染完成后发送给的信号
from flask import Flask, render_template, request from flask import template_rendered import config from signals import template_rendered_func app = Flask(__name__) app.config.from_object(config) template_rendered.connect(template_rendered_func) # 模板渲染完成后发送的信号 开始监听信号 @app.route('/') def index(): return render_template("html/index.html") @app.route('/login/') def login(): username = request.args.get("username") # 通过查询字符串的方式获取参数 if username: return "success login {}".format(username) else: return "Please Input Username" if __name__ == '__main__': app.run()
定义对应的处理函数:
def template_rendered_func(sender, template, context): """ # 函数参数 :param sender: :param template: :param context: :return: """ print("模板渲染完成") print("sender: ", sender) print("template: ", template) print("context: ", context)
输出信息:
sender: <Flask 'app'> template: <Template 'html/index.html'> context: {'g': <flask.g of 'app'>, 'request': <Request 'http://127.0.0.1:5000/' [GET]>, 'session': <SecureCookieSession {}>}
2. got_request_exception: 视图函数中发生异常时发送的信息
got_request_exception.connect(got_request_exception_func) def got_request_exception_func(sender, *args, **kwargs): """ :param sender: :param args: :param kwargs: :return: """ print(sender) print(args) print(kwargs)
输出信息:关键字参数以及位置参数的输出
<Flask 'app'> () {'exception': ZeroDivisionError('division by zero',)}
Flask所有的内置信号:
# Core signals. For usage examples grep the source code or consult # the API documentation in docs/api.rst as well as docs/signals.rst 1. template_rendered: 模板渲染完成后发送的信号 2. before_render_template :模板渲染前发送的信号 3. request_started :模板开始渲染 request_finished :模板渲染完成 request_tearing_down :对象被销毁的信号 got_request_exception :视图函数发生异常的信号,一般可以监听这个信号来记录网站异常信息 appcontext_tearing_down :app上下文被销毁的信号 appcontext_pushed :app上下文被推入LocalStack栈的信号 appcontext_popped :app上下文被推出LocalStack栈的信号 message_flashed :调用了Flask的“flashed”的信号
--------------------------------------------------------------------------------------------------------------------------------
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)