web框架——flask3.x-上下文管理机制

news/2025/12/8 21:15:29/文章来源:https://www.cnblogs.com/guohan222/p/19323677

flask3.x——上下文管理机制


1. 上下文隔离

  • 核心目标:解决多请求并发时的数据混乱问题,让每个请求的相关数据

  • 互不干扰,为接收请求时做环境铺垫。

  • 实现工具:用contextvars模块里面的contextvar类 解决线程/协程数据隔离问题

  • 作用:确保不同请求的的requestcurrent_app等数据各用各的,而不是串用

  • Contextvar简概:是python中用于实现线程/协程级数据隔离的核心工具,能让不同执行单元(如请求)拥有专属数据,互不干扰

    • 自动绑定标识:会自动关联当前线程/协程的唯一标识(线程/协程ID)

    • 精准数据定位:存取数据时根据唯一标识精确找到该执行单元的专属数据,而不会串拿其他单元的数据

    • 直接存储当前状态:通过_cv_app(应用上下文)和_cv_request(请求上下文)两个独立的ContextVar,直接存储当前活跃的上下文对象,不维护历史上下文的栈结构。即同一时刻只需要一个活跃的AppContext和一个活跃的RequestContext

    • 简化的嵌套模型:对于临时请求,不再通过嵌套栈实现,而是使用ContextVarset()方法直接替换当前上下文

    • 自动恢复ContextVarreset()方法可以自动恢复到之前的状态,相当于内置了"栈"的单个层级切换

     

2. 上下文管理机制(新旧版本对比)

  • Flask版本差异上下文管理机制说明:

    • Flask 2.x及更早版本:都依赖Werkzeug里面的Local.py 。Local类和LocalStack类配合实现上下文的隔离和管理,通过LocalStack类维护栈结构,管理嵌套上下文。

    • Flask 3.x及更新版本:不再依赖Werkzeug里面的Local.py。直接使用Python内置的ContextVar存储上下文对象,通过令牌管理嵌套上下文。

  • 上下文管理机制核心目标:

    • 多个请求的隔离

    • 单个请求的内的多个Context有序管理

  • 上下文管理机制实现方式差异:

    • Flask 3.x及更新版本:

      • 使用Python内置的ContextVar实现

      • 直接管理当前上下文状态,不再使用栈结构

      • 分别存储AppContextRequestContext,通过令牌结合上下文类的push/pop方法管理

    • Flask 2.x及更早版本:

      • 使用LocalStack类实现栈结构管理

      • Context对象按顺序压入栈中管理

      • 通过栈的后进先出特性实现多个Context的有序切换

  • 上下文管理机制新旧版本总结

    • Flask 3.x及更新版本总结:它通过_cv_app(应用上下文)和_cv_request(请求上下文)两个独立的ContextVar实例,直接存储当前活跃的上下文对象。当出现临时上下文操作时,会执行set()方法将临时上下文设为当前活跃状态,覆盖原有上下文值。当临时请求处理完后,执行reset()方法恢复到之前的上下文状态。整个过程结合try-finally块,确保无论是否发生异常,上下文都能正确恢复,避免上下文混乱。。

    • Flask 2.x及更早版本总结:通过_app_ctx_stack(应用上下文栈)和_request_ctx_stack(请求上下文栈)两个独立的LocalStack实例,以栈结构管理上下文对象。当出现临时上下文操作时,会执行栈的push()方法将临时上下文压入栈顶,使其成为当前活跃上下文,而当这个临时请求处理完后,执行栈的pop()方法移除栈顶的临时上下文,恢复到之前的上下文状态。整个过程结合try-finally块,确保无论是否发生异常,上下文都能正确出栈,避免栈结构混乱。

 

3. 上下文到底是什么及上下文管理机制的实现

  • 上下文:

    • 简而言之一篇文章甚至一部小说,试卷出题人经常会让你进行联系上下文分析作者当前写这句话的心情,此时就得联系上下文即根据文本中出现的信息来进行有逻辑有思维有步骤的分析然后进行作答。

    • 此过程中你肯定得记住这些信息,对应到Flask的上下文中其也就是用于存储信息然后进行程序的有内容有顺序的执行。然而要存储这些信息就有了请求上下文RequestContext和应用上下文Appcontext,而之后要进行程序有内容有顺序的执行就得需要上下文管理机制。

  • 上下文管理机制的实现:

    • 如果没有这个机制会怎么样:

      • 没有上下文管理机制ContextVar或者LocalStack,单个请求内的多段逻辑会因临时数据污染、上下文状态混乱而报错。

      • 比如现在有HTTP请求过来:首先创建请求上下文RequestContext再在其 push() 方法中创建/激活应用上下文AppContext,若后面有个临时上下文操作(比如代码中调用某个工具函数来完成某个功能),此时会新建临时 AppContext ,不会新增 RequestContextRequestContext 与当前用户请求强绑定,全程唯一,包含 request 、 session 等请求数据):

        • 若没有合适的上下文管理机制

          • Flask 2.x及更早版本:

            1. 依赖_app_ctx_stackAppContext 栈)和 _request_ctx_stackRequestContext 栈)管理上下文 。

            2. 没有LocalStack 将 AppContext 维护成栈,就无法区分主AppContext 和临时 AppContext ,销毁时可能误删主 AppContext ,导致主请求的后续操作无法正常进行。

          • Flask 3.x及更新版本:

            1. 依赖_cv_app (存储 AppContext)和 _cv_request (存储 RequestContext )两个 ContextVar管理上下文

            2. 没有 ContextVar 的Token状态记录时,临时操作后无法恢复主上下文状态,导致上下文混乱。

        • 视化

          • Flask 2.x及更早版本:_app_ctx_stack : [主AppContext, 临时AppContext],_request_ctx_stack : [主RequestContext] (始终唯一)

          • Flask 3.x及更新版本:通过 ContextVar 分别存储 ppContext RequestContext ,用Token记录 AppContext 的嵌套状态,实现与栈等价的上下文切换, RequestContext 保持唯一

 

4. 上下文管理机制源码分析

  • Flask 3.x及更新版本核心实现组件源码拆解

    • 上下文存储方式

      • 使用两个独立的ContextVar实例作为上下文存储容器

    # flask/globals.py
    ​
    _cv_app: ContextVar[AppContext] = ContextVar("flask.app_ctx")
    """
    _cv_app= ContextVar("flask.app_ctx")
    表示创建了一个名为_cv_app的Contextvar变量
    "flask.app_ctx"
    表示这个Contextvar数据隔离工具的标识名称为"flask.app_ctx"
    _cv_app:ContextVar[AppContext]
    表示这个变量的类型为Contextvar且这个变量只能存储AppContext类型的对象
    """
    ​
    _cv_request: ContextVar[RequestContext] = ContextVar("flask.request_ctx")
    """
    _cv_request= ContextVar("flask.request_ctx")
    表示创建了一个名为_cv_request的Contextvar变量
    "flask.request_ctx"
    表示这个Contextvar数据隔离工具的标识名称为"flask.app_ctx"
    _cv_request: ContextVar[RequestContext]
    表示这个变量的类型为Contextvar且这个变量只能存储RequestContext类型的对象
    """

    image

     

    • 上下文代理方式

      • 通过LocalProxy创建了一系列全局可访问的代理对象,简化上下文访问

    app_ctx: AppContext = LocalProxy(  # type: ignore[assignment]   _cv_app, unbound_message=_no_app_msg
    )
    current_app: Flask = LocalProxy(  # type: ignore[assignment]   _cv_app, "app", unbound_message=_no_app_msg
    )
    g: _AppCtxGlobals = LocalProxy(  # type: ignore[assignment]   _cv_app, "g", unbound_message=_no_app_msg
    )
    ​
    ​
    ​
    request_ctx: RequestContext = LocalProxy(  # type: ignore[assignment]   _cv_request, unbound_message=_no_req_msg
    )
    request: Request = LocalProxy(  # type: ignore[assignment]   _cv_request, "request", unbound_message=_no_req_msg
    )
    session: SessionMixin = LocalProxy(  # type: ignore[assignment]   _cv_request, "session", unbound_message=_no_req_msg
    )

     

    • 上下文类实现

      • AppContext

        • 应用上下文,用于存储应用级别的信息

      class AppContext:
      ​   def __init__(self, app: Flask) -> None:       """       将Flask实例保存到self.app中,便于以后应用上下文在其他地方可以便于访问应用实例       创建URL适配器,调用了app中create_url_adapter()方法,便于将请求的URL路径匹配到对应的视图函数       创建单次请求生命周期中的全局对象g,便于用于存储单次请求生命周期中重要且反复需要的数据       创建一个空列表,用于存储ContextVar操作的令牌,便于pop操作时ContextVar利用令牌恢复到之前的应用上下文状态       """       self.app = app       self.url_adapter = app.create_url_adapter(None)       self.g: _AppCtxGlobals = app.app_ctx_globals_class()       self._cv_tokens: list[contextvars.Token[AppContext]] = []
      ​   def push(self) -> None:       #绑定应用上下文到当前上下文       """       _cv_app.set(self):将当前(self)应用上下文设置为_cv_app:ContextVar的值,即表示其为当前活跃的上下文。且会                          返回一个Token(令牌)用于恢复之前上下文的状态信息       self._cv_tokens.append():将返回的Token添加到_cv_tokens列表中       appcontext_pushed.send():发送应用上下文推入信号        """       self._cv_tokens.append(_cv_app.set(self))       appcontext_pushed.send(self.app, _async_wrapper=self.app.ensure_sync)
      ​   def pop(self, exc: BaseException | None = _sentinel) -> None:  # type: ignore       #弹出应用上下文       """       pop时如果是最后一个令牌       获取当前异常(如果有)       调用应用的 do_teardown_appcontext() 方法,执行所有注册的应用上下文清理函数       """       try:           if len(self._cv_tokens) == 1:               if exc is _sentinel:                   exc = sys.exc_info()[1]               self.app.do_teardown_appcontext(exc)                      """       不论是否为最后一个上下文均执行finally块,确保上下文正确切换       ctx = _cv_app.get():从ContextVar中获取当前活跃的应用上下文       _cv_app.reset(self._cv_tokens.pop()):将Token列表中的令牌进行获取,进行恢复之前应用上下文的状态       """       finally:           ctx = _cv_app.get()           _cv_app.reset(self._cv_tokens.pop())
      ​              """       最后检查,finally块中获取的当前活跃的应用上下文是否为当前的应用上下文。如果不是则抛异常,确保上下文的正确切换       """       if ctx is not self:           raise AssertionError(               f"Popped wrong app context. ({ctx!r} instead of {self!r})"           )                  """       发送当前应用上下文弹出信号       """       appcontext_popped.send(self.app, _async_wrapper=self.app.ensure_sync)       
      ​              核心工作流程总结
      初始化:创建 AppContext 实例,存储应用实例、URL 适配器和全局对象 g
      推入上下文:
      调用 push() 方法
      将当前实例设置为全局 _cv_app ContextVar 的值
      存储返回的令牌
      发送应用上下文推入信号
      弹出上下文:
      调用 pop() 方法
      如果是最后一个令牌,执行应用上下文清理
      使用存储的令牌恢复 ContextVar 之前的状态
      验证弹出的上下文是否正确

       

      • RequestContext

        • 请求上下文,用于存储请求级别的信息

      class RequestContext:
      ​   def __init__(       self,       app: Flask,       environ: WSGIEnvironment,       request: Request | None = None,       session: SessionMixin | None = None,   ) -> None:       """       初始化,存储应用实例、request、路由适配器、flash、session等       """       self.app = app       self.request = request or app.request_class(environ)  # 请求对象       self.url_adapter = None       self.flashes = None       self.session = session  # 会话对象       self._after_request_functions = []  # 后处理函数列表self._cv_tokens: list[tuple[contextvars.Token[RequestContext], AppContext | None]] = []  # 令牌列表
      ​
      ​   def push(self) -> None:       #绑定请求上下文到当前上下文       """       应用上下文管理逻辑       1.检查确保有活跃应用上下文存在       2.如果,不存在或者应用上下文的应用实例和当前请求上下文的应用实例不一样则创建与当前请求上下文相符合的应用上下文       app_ctx = self.app.app_context():使用应用实例的app_context()方法返回AppContext对象       app_ctx.push():执行AppContext类的push方法将当前AppContext实例设置为全局 _cv_app ContextVar 的值       3.否则,不在创建新的应用上下文       4.最后存储请求上下文令牌,和应用上下文以元组形式将其归于一组到列表,用于管理嵌套上下文和状态恢复       """       app_ctx = _cv_app.get(None)       if app_ctx is None or app_ctx.app is not self.app:           app_ctx = self.app.app_context()           app_ctx.push()       else:           app_ctx = None       self._cv_tokens.append((_cv_request.set(self), app_ctx))
      ​              """       会话初始化逻辑       如果会话对象不存在       1.获取应用的会话接口 app.session_interface       2.调用open_session(self.app, self.request)尝试从请求中恢复会话       3.如果恢复失败 if self.session is None ,创建一个新的会话 make_null_session       """       if self.session is None:           session_interface = self.app.session_interface           self.session = session_interface.open_session(self.app, self.request)
      ​           if self.session is None:               self.session = session_interface.make_null_session(self.app)
      ​                      if self.url_adapter is not None:           self.match_request()
      ​   def pop(self, exc: BaseException | None = _sentinel) -> None:  # type: ignore       """       判断是否为最后一个请求上下文       clear_request = len(self._cv_tokens)       判断:clear_request == 1       """       clear_request = len(self._cv_tokens) == 1
      ​              """       pop时如果是最后一个请求上下文       获取当前异常(如果有)       调用do_teardown_request执行请求上下文清理函数       检查请求对象是否有close方法,如果有则调用关闭资源       """       try:           if clear_request:               if exc is _sentinel:                   exc = sys.exc_info()[1]               self.app.do_teardown_request(exc)
      ​               request_close = getattr(self.request, "close", None)               if request_close is not None:                   request_close()                                             """       无论是否有异常均执行finally块       1.获取当前请求上下文,并获取令牌便于恢复之前的请求上下文状态,和获取与之相关的应用上下文       2.利用令牌恢复之前的请求上下文状态       3.如果是最后一个请求上下文,将请求对象从WSGI环境中移除       4.如果app_ctx不为None(表示替换这个请求上下文时,创建过与之相配合的应用上下文),则清除这个应用上下文       5.确保弹出的请求上下文是当前实例,如果不是则抛异常              """       finally:           ctx = _cv_request.get()           token, app_ctx = self._cv_tokens.pop()           _cv_request.reset(token)
      ​           if clear_request:               ctx.request.environ["werkzeug.request"] = None
      ​           if app_ctx is not None:               app_ctx.pop(exc)
      ​           if ctx is not self:               raise AssertionError(                   f"Popped wrong request context. ({ctx!r} instead of {self!r})"               )                                      总结
      RequestContext类实现了Flask的请求上下文管理,核心功能包括:
      ​
      初始化请求相关的资源(请求对象、会话、URL适配器等)
      在push时确保应用上下文存在并正确管理嵌套上下文
      在pop时按正确顺序恢复上下文状态并清理资源
      通过令牌机制支持嵌套上下文的正确管理
      确保资源的正确释放和异常安全

image

image

 

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/993561.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JavaEE初阶——多线程(9)JUC的程序类和死锁

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

[智能体设计模式] 第 1 章:提示链(Prompt Chaining) - 实践

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

极速AI助手 - 多AI服务桌面助手, 支持MCP工具调用, 内置免费AI功能

极速AI助手是一款专业的桌面端多AI服务交互程序。支持接入多种主流AI服务(内置AI、DeepSeek、通义千问等),集成MCP工具调用功能,让AI助手能够执行更多实用任务。支持多对话管理、Markdown渲染、流式响应等功能,是…

蓝鲸花呗客服妙招帮你脱困省油大空间低配拆解银河的“水桶车细节值得吵一架

谁能想到,一台8万出头的B级插混轿车,竟能同时做到续航超2100km、百公里油耗2.67L、轴距2845mm? 吉利银河A7的上市,直接撕掉了“低价必低配”的标签,甚至让合资混动车型的定价逻辑彻底崩塌。有车主实测从北京到武汉一箱…

吴恩达深度学习课程四:计算机视觉 第一周:卷积基础知识(一)图像处理基础

此分类用于记录吴恩达深度学习课程的学习笔记。 课程相关信息链接如下:原课程视频链接:[双语字幕]吴恩达深度学习deeplearning.ai github课程资料,含课件与笔记:吴恩达深度学习教学资料 课程配套练习(中英)与答案…

Python函数基础实战教程:从定义调用到参数传值全解析

一、Python函数核心意义:为什么要学函数基础? Python函数是代码复用、逻辑封装的核心载体,也是新手从「线性代码编写」过渡到「模块化编程」的关键。无论是自动化脚本、数据分析还是Web开发,函数都能让代码更简洁、…

索引数组读取修改添加

索引数组读取修改添加1 $xm = array(小明,男,28,5888.88);2 3 //1.读取数据 4 echo $xm[0].同学的工资是:.$xm[3].元人民币。;5 6 //2.修改数据 7 $xm[0] = 小张;8 $xm[1]…

12.08

今天上午统一建模语言上机数构上课,下午Java

zsj_蓝桥python系列二_Python 基础语法 _Python 列表推导式

zsj_蓝桥python系列二_Python 基础语法 _Python 列表推导式Python 基础语法 Python 列表推导式 你有没有写过这样的代码?想生成一个新列表,得先建空列表、再写 for 循环、最后用append()加元素 —— 又长又麻烦。今天…

白带异常用药推荐:科学应对妇科炎症的健康指南

白带是女性生殖系统健康的“晴雨表”,正常情况下呈透明或白色糊状、无异味。当白带出现颜色异常(如黄绿色、灰色)、性状改变(如豆腐渣样、泡沫状)或伴随瘙痒、异味时,可能提示阴道炎、宫颈炎等妇科炎症。世界卫生…

获取数组长度即最大下标

获取数组长度即最大下标$xm = array(小明,男,28,5888.88);//count()函数用于返回数组长度(元素的个数),int(整型)$x = count($xm);echo $x;echo "<br>";var_dump($x);//3.使用数组长度添加数据$xm[c…

第49天

今天学习的java

JAVA学习笔记-DAY3

引用类型 VS 指针在Java中,引用类型的变量非常类似于C/C++的指针。 引用类型指向一个对象,指向对象的变量是引用变量。这些变量在声明时被指定为一个特定的类型,比如 Employee、Puppy 等。变量一旦声明后,类型就不…

北京婚姻家庭法律事务所服务观察:专业机构业务能力解析

在社会关系日益复杂化的当下,婚姻家庭领域的法律需求呈现多样化趋势,涵盖离婚纠纷、财产划分、子女抚育、遗产继承等多个维度。专业的婚姻家庭法律事务所凭借其对细分领域的深耕,为当事人提供法律支持与权益保障,成…

火小兔的两种交互方式与全部命令 - Magic

使用本平台开发脚本 从微软官方渠道安装完毕后,可以从开始菜单中搜索到“机关区”和“火小兔”。机关区——调试窗口(演示):直接双击“机关区”,进入界面之后就可以直接编写,调试,编辑,浏览,运行本平台命令;…

123_尚硅谷_匿名函数

123_尚硅谷_匿名函数1.匿名函数使用方式1:在定义匿名函数时就直接调用 2.匿名函数使用方式2:将匿名函数赋给一个变量,再通过该变量调用匿名函数 3.全局匿名函数

推荐几个模切机品牌:国内实力厂商推荐

模切机作为印后加工和精密制造领域的关键设备,广泛应用于印刷、包装、电子、图文处理等行业,其性能直接影响产品的加工精度、生产效率及成品质量。在各行业对加工工艺要求不断提升的背景下,选择技术成熟、品质可靠的…

白带异常用药品牌排行榜:科学守护女性生殖健康

白带是女性生殖系统健康的“晴雨表”,正常情况下呈无色透明或乳白色糊状,无异味。当出现颜色、质地、气味异常(如黄绿色、豆腐渣样、鱼腥味等)时,可能提示阴道炎症、宫颈病变或盆腔感染等问题。及时识别症状并采取…