深入探究 Flask 的应用和请求上下文
目标
读完本文后,您应该能够解释:
- 什么是上下文
- 哪些数据同时存储在应用程序和请求上下文中
- 在 Flask 中处理请求时,处理应用程序和请求上下文所需的步骤
- 如何使用应用程序和请求上下文的代理
- 如何在视图函数中使用
current_app和代理request - 什么是上下文本地
什么是上下文?
为了执行你编写的代码,需要处理数据。这些数据可能是配置数据、输入数据、数据库数据等等。
上下文用于跟踪代码需要执行的数据。
在 Flask 中,上下文用于提供处理请求和命令行界面 (CLI) 命令所需的数据。
虽然本文重点介绍处理请求,但所提出的概念也适用于 CLI 命令。
请求处理
让我们从高层的角度开始了解请求的处理方式:

因此,浏览器会向 Web 服务器(如 Nginx 或 Apache)发送请求,以请求特定的 URL(上图中的“/”URL)。然后,Web 服务器会将此请求路由到 WSGI 服务器进行处理。
WSGI 代表 Web 服务器网关接口,是 Web 服务器和基于 Python 的 Web 应用程序之间的接口。它是必需的,因为 Web 服务器无法直接与 Python 应用程序通信。有关详细信息,请查看WSGI。
WSGI 服务器告诉 Flask 应用程序处理请求。
Flask 应用程序生成一个响应,该响应被发送回 WSGI 服务器,再发送回 Web 服务器,最终发送回 Web 浏览器。
这些步骤描述了请求-响应周期,这是通过 Web 服务器、WSGI 应用服务器和 Web 应用程序处理请求的关键功能。
Flask 中的上下文
当收到请求时,Flask 提供两个上下文:
语境 描述 可用对象 应用 跟踪应用程序级数据(配置变量、记录器、数据库连接) current_app,g要求 跟踪请求级数据(URL、HTTP 方法、标头、请求数据、会话信息) request,session值得注意的是,上述每个对象通常被称为“代理”。这只是意味着它们是对象全局风格的代理。我们稍后会深入探讨这一点。
Flask 在收到请求时处理这些上下文的创建。它们可能会造成混淆,因为您并不总是能够根据应用程序所处的状态访问特定对象。
概览图
下图说明了处理请求时如何处理上下文:

第 1 步 - Web 和 WSGI 服务器
当 Web 服务器收到请求时,一切就开始了:

Web 服务器的工作是将传入的 HTTP 请求路由到WSGI服务器。
Apache和Nginx是两种常见的 Web 服务器,而Gunicorn、uWSGI和mod_wsgi是流行的 WSGI 服务器。
值得注意的是,虽然Flask 开发服务器是一个 WSGI 服务器,但它并不适合用于生产。
步骤 2 - 工作者
为了处理该请求,WSGI 服务器会生成一个工作进程来处理该请求:

工作线程可以是线程、进程或协程。例如,如果您使用 Flask Development Server 的默认配置,则工作线程将是线程。
如果您有兴趣了解有关 Python 中线程、多处理和异步之间的更多区别,请查看使用并发、并行和异步加速 Python文章和Python 中的并发视频。
对于这个解释,工作者类型并不重要;关于工作者的关键点是它一次处理一个请求(因此需要多个工作者)。
步骤 3 - 上下文
一旦执行切换到 Flask 应用程序,Flask 就会创建应用程序和请求上下文并将它们推送到各自的堆栈上:

回顾一下,应用程序上下文存储应用程序级数据,例如配置变量、数据库连接和记录器。同时,请求上下文存储需要处理以生成响应的特定于请求的数据。
可能会令人惊讶地看到,但两个堆栈都是作为全局对象实现的(这将在下一节中变得更加清晰)。
步骤 4 - 代理
现在 Flask 应用程序已准备好处理数据(在视图函数中),并且数据已在应用程序和请求上下文堆栈中准备就绪,我们需要一种方法来连接这两部分......代理来救援!

视图函数使用代理来访问应用程序(存储在应用程序上下文堆栈中)和请求上下文(存储在请求上下文堆栈中):
current_app- 代理工作者的应用程序上下文request- 代理工作者的请求上下文
乍一看,这个序列似乎令人困惑,因为视图函数似乎正在通过代理访问全局对象(应用程序和请求上下文堆栈)。如果是这样的话,这个操作就会有问题,因为它不是线程安全的。您可能还会认为这些堆栈(作为全局对象)可以被任何工作程序访问,这将是一个安全问题。
然而,这种设计是 Flask 的一大特色……堆栈被实现为上下文本地对象。
有关代理的更多信息,请查看Flask 文档中的代理注释和代理模式文章。
上下文局部变量
Python 有一个线程本地数据的概念,用于存储特定于线程的数据,它既是“线程安全的,又是线程唯一的”。换句话说,每个线程都能够以线程安全的方式访问数据,并且数据对于特定线程始终是唯一的。
Flask 实现了类似的行为(上下文本地),但是以更通用的方式允许工作者成为线程、进程或协程。
上下文局部变量实际上是在Werkzeug中实现的,它是 Flask 的关键包之一。为简单起见,我们在讨论上下文局部变量时将引用 Flask。
当数据存储在上下文本地对象中时,数据以只有一个工作进程可以检索的方式存储。因此,如果两个单独的工作进程访问上下文本地对象,它们将各自获取各自独有的特定数据。
下一节将介绍一个使用上下文本地对象的示例。
总而言之,每个视图函数中都有current_app和request代理,它们用于从各自的堆栈访问上下文,这些堆栈存储为上下文本地对象。
在应用程序和请求上下文堆栈中使用“堆栈”使这个概念比原来更加令人困惑。这些“堆栈”通常存储的上下文不超过一个。
使用的数据结构是堆栈,因为存在非常高级的场景(例如,内部重定向)需要多于一个的元素。
Flask 中代理的好处
如果你要从头开始创建自己的 Web 框架,那么你可能会考虑将应用程序和请求上下文传递到每个视图函数中,如下所示:
@app.route('/add_item', methods=['GET', 'POST'])
def add_item(application_context, request_context): # contexts passed in!if request_context.method == 'POST':# Save the form data to the database...application_context.logger.info(f"Added new item ({ request_context.form['item_name'] })!")...
事实上,许多 Web 框架都是这样工作的(包括Django)。
然而,Flask 提供了current_app和request代理,它们最终看起来像视图函数的全局变量:
from flask import current_app, request@app.route('/add_item', methods=['GET', 'POST'])
def add_item():if request.method == 'POST':# Save the form data to the database...current_app.logger.info(f"Added new item ({ request.form['item_name'] })!")...
通过使用这种方法,视图函数不需要将上下文作为参数传入;这种方法简化了视图函数定义。但它可能会引起混淆,因为您并不总是能够访问current_app和request代理,具体取决于您的应用程序所处的状态。
提醒:
current_app和request代理实际上不是全局变量;它们指向作为上下文本地实现的全局对象,因此代理对于每个工作者来说始终是唯一的。
第 5 步 - 清理
生成响应后,请求和应用程序上下文将从各自的堆栈中弹出:

此步骤清理堆栈。
然后将响应发送回 Web 浏览器,完成对该请求的处理。
上下文局部变量
上下文本地对象是使用本地对象实现的,可以像这样创建:
$ python>>> from werkzeug.local import Local
>>> data = Local()
>>> data.user = 'pkennedy@hey.com'
每个上下文(即上一节中讨论的“工作者”)都可以访问一个Local对象,用于上下文独有的数据存储。所访问的数据对于上下文来说是唯一的,并且只能由该上下文访问。
LocalStack对象与Local对象类似,但是保留一个对象堆栈以允许push()和pop()操作。
LocalStack在上一节中,我们了解了在 Flask 中处理请求时如何使用应用程序上下文堆栈和请求上下文堆栈。这些堆栈在 Flask 中作为全局内存中的对象实现。
为了帮助巩固上下文局部变量的工作原理,让我们通过一个例子来说明如何LocalStack在全局内存中创建一个对象,然后让三个独立的线程访问它:

以下是该示例的完整脚本:
"""
Example script to illustrate how a global `LocalStack` object can be used
when working with multiple threads.
"""
import random
import threading
import timefrom werkzeug.local import LocalStack# Create a global LocalStack object for storing data about each thread
thread_data_stack = LocalStack()def long_running_function(thread_index: int):"""Simulates a long-running function by using time.sleep()."""thread_data_stack.push({'index': thread_index, 'thread_id': threading.get_native_id()})print(f'Starting thread #{thread_index}... {thread_data_stack}')time.sleep(random.randrange(1, 11))print(f'LocalStack contains: {thread_data_stack.top}')print(f'Finished thread #{thread_index}!')thread_data_stack.pop()if __name__ == "__main__":threads = []# Create and start 3 threads that each run long_running_function()for index in range(3):thread = threading.Thread(target=long_running_function, args=(index,))threads.append(thread)thread.start()# Wait until each thread terminates before the script exits by# 'join'ing each threadfor thread in threads:thread.join()print('Done!')
该文件创建一个LocalStack对象(thread_data_stack)用于存储将要创建的每个线程的数据。
thread_data_stack模仿 Flask 中的应用程序上下文堆栈或请求上下文堆栈。
long_running_function在每个线程中运行:
def long_running_function(thread_index: int):"""Simulates a long-running function by using time.sleep()."""thread_data_stack.push({'index': thread_index, 'thread_id': threading.get_native_id()})print(f'Starting thread #{thread_index}... {thread_data_stack}')time.sleep(random.randrange(1, 11))print(f'LocalStack contains: {thread_data_stack.top}')print(f'Finished thread #{thread_index}!')thread_data_stack.pop()
该函数将有关线程的数据推送到thread_data_stack全局内存中的对象:
thread_data_stack.push({'index': thread_index, 'thread_id': threading.get_native_id()})
此操作模仿将应用程序或请求上下文推送到其各自的堆栈。
函数完成后time.sleep(),将访问以下数据thread_data_stack:
print(f'LocalStack contains: {thread_data_stack.top}')
此操作模仿使用
app_context和request代理,因为这些代理访问其各自堆栈顶部的数据。
在函数的末尾,数据从中弹出thread_data_stack:
thread_data_stack.pop()
此操作模拟从各自的堆栈中弹出应用程序或请求上下文。
脚本运行时会启动3个线程:
# Create and start 3 threads that each run long_running_function()
for index in range(3):thread = threading.Thread(target=long_running_function, args=(index,))threads.append(thread)thread.start()
并且join每个线程都等待,直到每个线程完成执行:
# Wait until each thread terminates before the script exits by
# 'join'ing each thread
for thread in threads:thread.join()
让我们运行这个脚本看看会发生什么:
$ python app.pyStarting thread #0... <werkzeug.local.LocalStack object at 0x109cebc40>
Starting thread #1... <werkzeug.local.LocalStack object at 0x109cebc40>
Starting thread #2... <werkzeug.local.LocalStack object at 0x109cebc40>
LocalStack contains: {'index': 0, 'thread_id': 320270}
Finished thread #0!
LocalStack contains: {'index': 1, 'thread_id': 320271}
Finished thread #1!
LocalStack contains: {'index': 2, 'thread_id': 320272}
Finished thread #2!
Done!
每个线程真正有趣的是它们都指向LocalStack内存中的同一个对象:
Starting thread #0... <werkzeug.local.LocalStack object at 0x109cebc40>
Starting thread #1... <werkzeug.local.LocalStack object at 0x109cebc40>
Starting thread #2... <werkzeug.local.LocalStack object at 0x109cebc40>
当每个线程访问 时thread_data_stack,访问都是该线程独有的LocalStack!这就是(和)的魔力Local——它们允许上下文独有的访问:
LocalStack contains: {'index': 0, 'thread_id': 320270}
LocalStack contains: {'index': 1, 'thread_id': 320271}
LocalStack contains: {'index': 2, 'thread_id': 320272}
与典型的全局内存访问不同,对的访问
thread_data_stack也是线程安全的。
结论
Flask 的一个强大(但令人困惑)的方面是如何处理应用程序和请求上下文。希望本文能对这个主题提供一些澄清!
应用程序和请求上下文在处理请求或 CLI 命令时提供必要的数据。确保使用current_app和request代理来访问应用程序上下文和请求上下文。
相关文章:
深入探究 Flask 的应用和请求上下文
目标 读完本文后,您应该能够解释: 什么是上下文哪些数据同时存储在应用程序和请求上下文中在 Flask 中处理请求时,处理应用程序和请求上下文所需的步骤如何使用应用程序和请求上下文的代理如何在视图函数中使用current_app和代理request什么…...
C++学习笔记(30)
二十三、随机数 在实际开发中,经常用到随机数,例如:纸牌的游戏洗牌和发牌、生成测试数据等。 函数原型: void srand(unsigned int seed); // 初始化随机数生成器(播种子)。 int rand(); // 获一个取随机数。…...
Rust GUI框架 tauri V2 项目创建
文章目录 Tauri 2.0创建应用文档移动应用开发 Android 前置要求移动应用开发 iOS 前置要求参考资料 Tauri 2.0 Tauri 是一个构建适用于所有主流桌面和移动平台的轻快二进制文件的框架。开发者们可以集成任何用于创建用户界面的可以被编译成 HTML、JavaScript 和 CSS 的前端框架…...
C++继承(上)
1.继承的概念 继承是一个类继承另外一个类,称继承的类为子类/派生类,被继承的类称为父类/基类。 比如下面两个类,Student和Person,Student称为子类,Person称为父类。 #include<iostream> using namespace std…...
在 Vim 中打开文件并快速查询某个字符
在 Vim 中打开文件并快速查询某个字符,可以按照以下步骤操作: 打开 Vim 并加载文件: vim your_file.txt将 your_file.txt 替换为你要查询的文件名。 进入普通模式(如果你还在插入模式或其他模式下): Es…...
oracle 条件取反
在Oracle数据库中,条件取反主要通过逻辑运算符NOT来实现。NOT是一个单目运算符,用于对指定的条件表达式取反。当条件表达式为真(True)时,NOT运算符的结果就是假(False);反之…...
力扣最热一百题——缺失的第一个正数
目录 题目链接:41. 缺失的第一个正数 - 力扣(LeetCode) 题目描述 示例 提示: 解法一:标记数组法 1. 将非正数和超出范围的数替换 2. 使用数组下标标记存在的数字 3. 找到第一个未标记的位置 4. 为什么时间复杂…...
零基础入门AI:一键本地运行各种开源大语言模型 - Ollama
什么是 Ollama? Ollama 是一个可以在本地部署和管理开源大语言模型的框架,由于它极大的简化了开源大语言模型的安装和配置细节,一经推出就广受好评,目前已在github上获得了46k star。 不管是著名的羊驼系列,还是最新…...
3.接口测试的基础/接口关联(Jmeter工具/场景一:我一个人负责所有的接口,项目规模不大)
一、Jmeter接口测试实战 1.场景一:我一个人负责所有的接口:项目规模不大 http:80 https:443 接口文档一般是开发给的,如果没有那就需要抓包。 请求默认值: 2.请求: 请求方式:get,post 请求路径 请求参数 查询字符串参数…...
【matlab】将程序打包为exe文件(matlab r2023a为例)
文章目录 一、安装运行时环境1.1 安装1.2 简介 二、打包三、打包文件为什么很大 一、安装运行时环境 使用 Application Compiler 来将程序打包为exe,相当于你使用C编译器把C语言编译成可执行程序。 在matlab菜单栏–App下面可以看到Application Compiler。 或者在…...
从底层原理上解释clickhouse查询为什么快
ClickHouse 是一个开源的列式数据库管理系统,以其极高的查询性能著称。为了理解 ClickHouse 查询为什么快,我们需要从以下几个方面进行深入探讨,包括其架构设计、存储引擎、索引结构、并行化策略以及内存管理等底层原理。 1. 列式存储&#…...
FEAD:fNIRS-EEG情感数据库(视频刺激)
摘要 本文提出了一种可用于训练情绪识别模型的fNIRS-EEG情感数据库——FEAD。研究共记录了37名被试的脑电活动和脑血流动力学反应,以及被试对24种情绪视听刺激的分类和维度评分。探讨了神经生理信号与主观评分之间的关系,并在前额叶皮层区域发现了显著的…...
标准库标头 <bit>(C++20)学习
<bit>头文件是数值库的一部分。定义用于访问、操作和处理各个位和位序列的函数。例如,有函数可以旋转位、查找连续集或已清除位的数量、查看某个数是否为 2 的整数幂、查找表示数字的最小位数等。 类型 endian (C20) 指示标量类型的端序 (枚举) 函数 bit_ca…...
redis群集三种模式:主从复制、哨兵、集群
redis群集有三种模式 redis群集有三种模式,分别是主从同步/复制、哨兵模式、Cluster,下面会讲解一下三种模式的工作方式,以及如何搭建cluster群集 ●主从复制:主从复制是高可用Redis的基础,哨兵和集群都是在主从复制…...
【MATLAB源码-第225期】基于matlab的计算器GUI设计仿真,能够实现基础运算,三角函数以及幂运算
操作环境: MATLAB 2022a 1、算法描述 界面布局 计算器界面的主要元素分为几大部分:显示屏、功能按钮、数字按钮和操作符按钮。 显示屏 显示屏(Edit Text):位于界面顶部中央,用于显示用户输入的表达式和…...
基于yolov8的红外小目标无人机飞鸟检测系统python源码+onnx模型+评估指标曲线+精美GUI界面
【算法介绍】 基于YOLOv8的红外小目标无人机与飞鸟检测系统是一项集成了前沿技术的创新解决方案。该系统利用YOLOv8深度学习模型的强大目标检测能力,结合红外成像技术,实现了对小型无人机和飞鸟等低空飞行目标的快速、准确检测。 YOLOv8作为YOLO系列的…...
网络封装分用
目录 1,交换机 2,IP 3,接口号 4,协议 分层协议的好处: 5,OSI七层网络模型. 6,TCP/IP五层网络模型(主流): [站在发送方视角] [接收方视角] 1,交换机 交换机和IP没有关系,相当于是对路由器接口的扩充,这时相当于主机都与路由器相连处于局域网中,把越来越多的路由器连接起…...
【Finetune】(一)、transformers之BitFit微调
文章目录 0、参数微调简介1、常见的微调方法2、代码实战2.1、导包2.2、加载数据集2.3、数据集处理2.4、创建模型2.5、BitFit微调*2.6、配置模型参数2.7、创建训练器2.8、模型训练2.9、模型推理 0、参数微调简介 参数微调方法是仅对模型的一小部分的参数(这一小部分可…...
ubuntu24系统普通用户免密切换到root用户
普通用户登录系统后需要切换到root用户,这边需要密码,现在不想让用户知道密码是多少。 sudo: 1 incorrect password attempt $ su - Password: root-security-cm5:~#开始配置普通用户免密切换到root用户,编辑配置文件 /etc/sudoers 最后增加…...
如何应对pcdn技术中遇到的网络安全问题?
在应对网络安全问题时,需要采取一系列的操作措施,以确保网络环境的稳定性和数据的安全性。以下是一些建议: 选择可靠的PCDN提供商:与有良好安全记录的PCDN提供商合作,确保提供商具备专业的安全团队,能够提…...
label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...
Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...
dedecms 织梦自定义表单留言增加ajax验证码功能
增加ajax功能模块,用户不点击提交按钮,只要输入框失去焦点,就会提前提示验证码是否正确。 一,模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...
【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
MySQL 8.0 OCP 英文题库解析(十三)
Oracle 为庆祝 MySQL 30 周年,截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始,将英文题库免费公布出来,并进行解析,帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...
多种风格导航菜单 HTML 实现(附源码)
下面我将为您展示 6 种不同风格的导航菜单实现,每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...
SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)
上一章用到了V2 的概念,其实 Fiori当中还有 V4,咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务),代理中间件(ui5-middleware-simpleproxy)-CSDN博客…...
LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》
这段 Python 代码是一个完整的 知识库数据库操作模块,用于对本地知识库系统中的知识库进行增删改查(CRUD)操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 📘 一、整体功能概述 该模块…...
C#中的CLR属性、依赖属性与附加属性
CLR属性的主要特征 封装性: 隐藏字段的实现细节 提供对字段的受控访问 访问控制: 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性: 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑: 可以…...
MySQL 知识小结(一)
一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库,分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷,但是文件存放起来数据比较冗余,用二进制能够更好管理咱们M…...
