Python 高阶语法(二):上下文管理器、with、yield 与资源安全释放——从文件到 FastAPI 数据库会话
这一篇我们会学习另一个在后端中非常重要的能力with__enter____exit__contextmanageryieldasync with它们会解释为什么文件、数据库连接、HTTP 客户端、模型资源都需要被正确打开和关闭。一、前言在编程中有很多资源不能只打开、不关闭。例如文件 数据库连接 网络连接 HTTP 客户端 Redis 客户端 消息队列连接 模型文件 线程锁如果资源没有被正确释放轻则出现文件无法删除、数据库连接耗尽重则服务运行一段时间后崩溃。在这里Python 提供了非常优雅的资源管理方式使用with...你在后端项目中已经见过类似思想def get_db(): db SessionLocal() try: yield db finally: db.close()这一篇就从最简单的文件操作开始逐步理解什么是资源管理为什么不能只 open 不 closewith 到底做了什么__enter__ 和 __exit__ 是什么contextmanager 和 yield 如何工作async with 和普通 with 有什么区别FastAPI 的 get_db 和 lifespan 为什么与它有关二、什么是资源为什么需要释放资源可以理解成程序向操作系统、数据库或外部服务“借来的东西”。例如打开一个文件file open(notes.txt, r, encodingutf-8)程序得到一个文件对象并获得读取文件的能力。当读取结束后需要file.close()告诉系统文件已经用完可以释放相关资源了。如果你只打开而不关闭可能出现文件句柄不断累积Windows 下文件可能被占用无法重命名、移动或删除文件程序长期运行后资源耗尽数据库连接同理后端请求到来 ↓ 创建数据库 Session ↓ 查询或修改数据 ↓ 请求结束 ↓ 必须关闭 Session如果每个请求都连接数据库却从不关闭连接会越来越多最终数据库拒绝新的请求。三、最原始的文件操作方式假设有一个文件notes.txt内容是学习 Python学习 FastAPI学习 SQLAlchemy最基础的读取方式file open(notes.txt, r, encodingutf-8) content file.read() file.close() print(content)这里的open()表示打开文件note.txt是文件的路径r是表示读取模式read。encodingutf-8表示按 UTF-8 编码读取中文文本。file.read()读取文件全部内容。file.close()表示关闭文件。这种写法可以运行但有一个问题如果读取过程出错close()可能来不及执行。例如file open(notes.txt, r, encodingutf-8) content file.read() result 1 / 0 file.close()程序执行到result 1 / 0时就报错并中断ZeroDivisionError: division by zero下面的file.close()不会执行。所以下面来讲解遇到这种情况我们应该如何规避。3.1 try / finally无论是否报错都释放资源为了保证资源一定关闭可以写file open(notes.txt, r, encodingutf-8) try: content file.read() print(content) finally: file.close()finally的含义是无论try中成功、失败、抛异常都会执行。四、withPython 推荐的资源管理写法对于文件或者数据库的连接关闭其实过于繁琐所以python中提供了更加简洁的方式with open(notes.txt, r, encodingutf-8) as file: content file.read() print(content)这段代码等价于“打开文件、使用文件、最后自动关闭文件”。其中with open(...) as file:可以理解为打开一个资源并把它暂时赋值给变量file当缩进代码块结束时自动执行清理工作。不管读取成功还是中间报错文件最终都会被关闭。因此文件操作推荐优先写成with open(notes.txt, r, encodingutf-8) as file: content file.read()4.1as file是什么语法右侧的open(notes.txt, r, encodingutf-8)表示创建文件对象。as file把这个文件对象赋值给变量file。所以缩进内部可以使用file.read() file.readline() file.write(...)例如逐行读取with open(notes.txt, r, encodingutf-8) as file: for line in file: print(line.strip())这里的line.strip()会去掉每行末尾的换行符和多余空格。输出学习 Python 学习 FastAPI 学习 SQLAlchemy五、上下文管理器是什么能配合with使用的对象叫做上下文管理器。例如open()返回的文件对象就是上下文管理器。一个上下文管理器需要支持两个特殊方法__enter__ __exit__它们分别对应进入 with 代码块之前退出 with 代码块之后执行逻辑可以理解为resource context_manager.__enter__() try: 使用 resource finally: context_manager.__exit__(...)因此with 某个上下文管理器 as resource: 使用 resource本质上是一种更安全、更清晰的try / finally写法。六、自定义上下文管理器统计代码执行时间我们自己实现一个上下文管理器。from time import perf_counter class Timer: def __enter__(self): self.start perf_counter() print(开始计时) return self def __exit__(self, exc_type, exc_value, traceback): end perf_counter() print(f执行耗时{end - self.start:.6f} 秒) return False使用with Timer(): total sum(range(1_000_000)) print(total)执行流程进入 with ↓ 执行 Timer.__enter__() ↓ 执行 with 缩进内部的代码 ↓ 退出 with ↓ 执行 Timer.__exit__()这里return self表示把当前Timer对象交给as后面的变量。with Timer() as timer: total sum(range(1_000_000))此时timer就是Timer对象。6.1__exit__的三个参数def __exit__(self, exc_type, exc_value, traceback):这三个参数与异常有关。如果with块正常结束exc_type None exc_value None traceback None如果with块发生异常例如with Timer(): result 1 / 0那么exc_type就表示异常类型例如ZeroDivisionError。而exc_value就表示异常对象traceback表示错误发生的位置追踪信息。大多数情况下return False表示不要吞掉异常仍然让错误继续抛出。这通常是推荐做法。如果写return True表示“我已经处理这个异常不再往外抛”。例如class IgnoreError: def __enter__(self): print(开始执行) def __exit__(self, exc_type, exc_value, traceback): if exc_type is not None: print(f捕获到异常{exc_value}) return True使用with IgnoreError(): result 1 / 0 print(程序仍然继续执行)程序不会因为除零错误停止。但真实项目中不要随意吞掉异常。否则程序表面正常问题却被隐藏排查会非常困难。七、contextlib.contextmanager用 yield 快速创建上下文管理器如果每次都手写__enter__ __exit__有时会比较冗长。Python 标准库提供了contextlib.contextmanager它可以通过生成器和yield更简洁地创建上下文管理器。例如from contextlib import contextmanager contextmanager def open_text_file(path, moder): file open(path, mode, encodingutf-8) try: yield file finally: file.close()使用with open_text_file(notes.txt) as file: content file.read() print(content)这个函数执行顺序特别重要contextmanager def open_text_file(...): file open(...) try: yield file finally: file.close()可以理解为进入 with ↓ 执行 yield 前打开文件 ↓ yield file把文件对象交给 as file ↓ 执行 with 内部代码 ↓ 退出 with ↓ 从 yield 后继续执行 ↓ finally 中关闭文件这和你项目中的get_db()已经非常接近了。八、用上下文管理器写文件下面是一个安全写入文本的例子from contextlib import contextmanager contextmanager def open_text_file(path, moder): file open(path, mode, encodingutf-8) try: yield file finally: file.close() with open_text_file(study_notes.txt, w) as file: file.write(今天学习了 Python 上下文管理器。\n) file.write(理解了 with、yield 和 finally 的关系。\n)其中w表示写入模式。需要注意w 模式会覆盖原文件内容 a 模式会在文件末尾追加内容 r 模式用于读取文件如果你想保留旧内容并新增笔记通常使用a。例如with open_text_file(study_notes.txt, a) as file: file.write(下一步学习 FastAPI 数据库连接管理。\n)九、数据库连接为什么特别适合上下文管理器在我们的 FastAPI 小项目中有def get_db(): db SessionLocal() try: yield db finally: db.close()它和刚刚的文件管理代码本质一样。文件版本file open(...) try: yield file finally: file.close()数据库版本db SessionLocal() try: yield db finally: db.close()其中SessionLocal()创建一份当前请求可用的数据库 Session。yield db把这个 Session 提供给 FastAPI 路由函数。db.close()在请求结束时关闭数据库会话。因此注册接口def register(user_in: UserRegister, db: DbSession): ...执行时会发生用户请求注册接口 ↓ FastAPI 调用 get_db() ↓ db SessionLocal() ↓ yield db把 db 注入 register() ↓ register() 查询、添加、提交用户数据 ↓ 请求结束 ↓ 执行 db.close()这就是为什么db能在注册函数中使用又不会永远占用数据库连接。9.1 为什么数据库操作还需要 commit、rollbackclose()的作用是关闭 Session但它不等于提交数据。例如db.add(user) db.commit()其中db.add(user)把对象加入当前数据库事务。db.commit()真正提交事务把数据写入数据库。如果执行过程中发生错误理想情况下还需要db.rollback()撤销当前未提交或失败的事务状态。更完整的数据库依赖可以写成def get_db(): db SessionLocal() try: yield db except Exception: db.rollback() raise finally: db.close()这里except Exception:表示捕获执行过程中的异常。db.rollback()表示回滚当前事务。raise表示继续把原始异常抛出去让 FastAPI 处理并记录。finally: db.close()无论成功或失败最终关闭 Session。实际项目中事务由哪一层负责提交需要统一规范。例如小项目中可以在路由函数中明确写db.add(task) db.commit()这样读代码时非常直观。复杂项目中也可能将事务管理统一封装在 Service 层或上下文管理器中。关键原则是一个业务操作的提交与回滚边界必须清楚不能混乱地到处 commit。9.2 数据库 Session 上下文管理器示例下面是一个独立的 Session 管理器示例from contextlib import contextmanager contextmanager def session_scope(): db SessionLocal() try: yield db db.commit() except Exception: db.rollback() raise finally: db.close()使用with session_scope() as db: user User( usernamekankan, password_hashexample_hash, ) db.add(user)正常情况下创建 Session ↓ yield db ↓ db.add(user) ↓ with 块结束 ↓ db.commit() ↓ db.close()如果中途报错创建 Session ↓ yield db ↓ 执行数据库操作时发生异常 ↓ db.rollback() ↓ db.close() ↓ 异常继续抛出这个模式非常适合脚本、后台任务或批量导入。但在 FastAPI 项目中如果多个地方都自动提交同时路由中又手动db.commit()就会造成事务边界不清晰。因此在 Web 项目中团队通常会统一一种规则例如规则一路由或 Service 显式 commit 规则二统一事务上下文自动 commit选择一种并坚持使用比混用更重要。十、async with异步资源管理前面使用的是with它适用于同步资源。如果资源的打开或关闭过程本身需要等待网络例如异步 HTTP 请求、异步数据库连接就需要async with例如import httpx async def get_weather(city): async with httpx.AsyncClient() as client: response await client.get( fhttps://example.com/weather?city{city} ) return response.json()这里async with httpx.AsyncClient() as client:表示创建异步 HTTP 客户端。await client.get(...)表示等待网络请求返回。当缩进代码块结束时HTTP 客户端会自动关闭连接。同步与异步的对比with 某资源 as resource: 使用同步资源async with 某资源 as resource: 使用异步资源如果资源需要await才能打开或关闭就不能使用普通with。十一、FastAPI 的 lifespan 与asynccontextmanager首先asynccontextmanager就是异步版本的contextmanagerlifespan是 FastAPI 专门接收这个异步上下文管理器的参数用来管理整个 Web 服务从启动到关闭的完整生命周期靠yield切成两段yield前面服务启动阶段在接收任何接口请求之前执行做初始化yield后面服务关闭阶段服务不再接收新请求之后执行做资源清理。lifespan包裹整个 Web 服务服务运行期间就卡在 yield 这里。服务停止才执行 yield 后的清理。你当前项目中的启动逻辑是from contextlib import asynccontextmanager asynccontextmanager async def lifespan(app: FastAPI): os.makedirs(data, exist_okTrue) Base.metadata.create_all(bindengine) yield再传给 FastAPIapp FastAPI( titleFastAPI 注册登录 JWT 实战, lifespanlifespan, )它和contextmanager的思路相同只是这里是异步版本。执行过程FastAPI 服务启动 ↓ 执行 yield 前的代码 ↓ 创建 data 文件夹 ↓ 创建 users、tasks 数据表 ↓ 执行到 yield服务开始接收请求 ↓ 服务关闭 ↓ 从 yield 后继续执行 ↓ 完成收尾工作如果未来需要在服务关闭时释放资源可以写asynccontextmanager async def lifespan(app: FastAPI): print(服务启动加载模型或建立连接) yield print(服务关闭释放模型或关闭连接)在 AI 应用中lifespan 常用于启动时加载 Embedding 模型 启动时创建向量数据库客户端 启动时读取配置 启动时建立模型服务连接 关闭时释放资源 关闭 HTTP 客户端 关闭模型连接 写入最终日志十二、真实 AI 项目中的上下文管理器案例假设未来你写一个“上传文档并构建知识库”的接口。处理过程可能包含接收 PDF ↓ 读取文件 ↓ 提取文本 ↓ 切分文档 ↓ 生成向量 ↓ 写入向量数据库 ↓ 保存文档状态其中至少有三类资源上传文件 数据库 Session HTTP / 模型客户端思路上可能是def process_document(file_path): with open(file_path, r, encodingutf-8) as file: content file.read() chunks split_text(content) with session_scope() as db: document Document( filenamefile_path, statusprocessing, ) db.add(document) return chunks如果模型调用是异步的async def create_embeddings(texts): async with httpx.AsyncClient() as client: response await client.post( https://example.com/embeddings, json{texts: texts}, ) return response.json()这就是上下文管理器在真实 AI 后端中的位置正确申请资源 ↓ 正确使用资源 ↓ 正确释放资源十三、常见错误总结13.1 打开文件后忘记关闭不推荐file open(notes.txt) content file.read()推荐with open(notes.txt, encodingutf-8) as file: content file.read()13.2 误以为close()等于commit()db.close()只负责关闭 Session。db.commit()才负责提交数据库事务。13.3 在__exit__中随意return True这会吞掉异常可能让错误悄悄消失。除非你非常明确要处理异常否则推荐return False或者不显式返回。13.4 将耗时 CPU 任务放进with就以为不会阻塞with的作用是资源管理不是并发工具。例如 PDF 切分、批量向量化特别耗时仍然应该考虑后台 Worker 任务队列 多进程13.5 不理解yield的暂停与恢复在上下文管理器或 FastAPI 依赖中yield resource不是彻底结束函数。它表示先把资源交出去 ↓ 等待外部代码执行 ↓ 再回来执行 yield 后的清理代码十九、本文总结这一篇最重要的链路是资源需要被正确释放 ↓ try / finally 可以保证清理 ↓ with 是更优雅的资源管理写法 ↓ __enter__ 在进入 with 前执行 __exit__ 在离开 with 后执行 ↓ contextmanager 可以用 yield 简化写法 ↓ asynccontextmanager 对应异步资源生命周期 ↓ FastAPI 的 get_db 和 lifespan 都应用了相同思想你现在再看项目中的代码def get_db(): db SessionLocal() try: yield db finally: db.close()应该能清楚理解创建数据库 Session ↓ 把 Session 交给接口使用 ↓ 请求结束后关闭 Session这不是 FastAPI 特有的魔法而是 Python 上下文管理、生成器和异常处理共同形成的工程能力。

相关新闻

第16章-RAG

第16章-RAG

第 16 章:RAG 在模型回答前检索可信外部知识,把相关证据加入 Prompt,让回答更贴近私有数据并具备可追溯来源。 前言 模型参数中没有企业最新制度、内部文档和实时知识。RAG(Retrieval-Augmented Generation)不要求重新…

2026/10/10 2:58:07 阅读更多 →
终于有人把数据指标体系讲明白了:指标怎么定、怎么分、怎么用一次讲透

终于有人把数据指标体系讲明白了:指标怎么定、怎么分、怎么用一次讲透

很多企业做数据分析,做到最后都会遇到一个很奇怪的问题:报表越来越多,指标越来越多,但企业反而越来越说不清自己的经营状况。财务有一套收入,销售有一套收入,运营还有另一套收入;老板问一句“这…

2026/10/10 2:58:07 阅读更多 →
【会议征稿】第三届数字经济与计算机科学国际学术会议(DECS 2026)

【会议征稿】第三届数字经济与计算机科学国际学术会议(DECS 2026)

第三届数字经济与计算机科学国际学术会议 (DECS 2026) 2026 3rdInternational Conference on Digital Economy and Computer Science 会议官网: 第三届数字经济与计算机科学国际学术会议(DECS 2026)https://ais.cn/…

2026/10/10 2:57:07 阅读更多 →

最新新闻

PyTorch+LSTM电影评论情感分析实战:从预处理到模型部署

PyTorch+LSTM电影评论情感分析实战:从预处理到模型部署

简介:一份评审分达99分的基于深度学习的电影评论情感分析项目资源包,适合计算机相关专业课程设计、期末大作业及入门实战,重点解决从数据爬取到模型训练演示中缺完整代码、缺数据集、缺文档的常见问题。资源围绕豆瓣短评设计,约5万…

2026/10/10 3:47:26 阅读更多 →
ruff + mypy + 模型:构建双轨代码审查流水线

ruff + mypy + 模型:构建双轨代码审查流水线

先聊一个现象:不少团队的代码评审还是“人在盯”,静态检查工具只跑了个摆设,规则集是默认的,类型标注是稀稀拉拉的,AI模型要么没接,要么接了也只是把diff丢给模型让它“帮忙看看”。直到我最近把一个内部数…

2026/10/10 3:47:26 阅读更多 →
OpenClaw智能体实战:从零搭建可运行的多步任务智能体

OpenClaw智能体实战:从零搭建可运行的多步任务智能体

简介:这份PDF资料源自厦门大学大数据教学团队的大模型科普讲座,面向希望系统理解人工智能与智能体应用的高校师生、科研人员及技术爱好者。内容从1950年图灵测试与1956年达特茅斯会议讲起,梳理人工智能六大发展阶段与未来五个阶段预测&#x…

2026/10/10 3:47:26 阅读更多 →
Flink性能调优:从并行度到状态管理的实战经验

Flink性能调优:从并行度到状态管理的实战经验

在大数据这个圈子里,“数据处理效率”是最常被挂在嘴边的一句话,但真正能在高吞吐、低延迟、可恢复性这三个方向同时站住的引擎,Flink绕不开。我第一次系统性使用Flink是在一个实时数仓项目里,数据源是几千万级的订单行为流&#…

2026/10/10 3:47:26 阅读更多 →
面向对象基础详解:类与对象、三大特性及常见面试坑

面向对象基础详解:类与对象、三大特性及常见面试坑

我最早接触面向对象,是在某家IT培训机构的基础班上。当时老师放了一张PPT,上面写着“面向对象三大特性:封装、继承、多态”,下面坐着的同学一半在记笔记,一半在发呆。我也是发呆的那一半——封装是啥?继承谁…

2026/10/10 3:47:26 阅读更多 →
从“无标题”到成品:内容项目定位与执行全流程

从“无标题”到成品:内容项目定位与执行全流程

“无标题”这三个字,可能是很多内容项目最真实的起点。文档是新建的,文件夹是空的,脑子里堆着七八个点子,但项目名称、内容方向、目标用户全都没有定下来。我经手过不少这样的盘子,最容易翻车的地方不在后面执行&#…

2026/10/10 3:46:25 阅读更多 →

日新闻

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

1. 从“卫星轨道分类”这个标题说起:为什么值得花时间搞懂第一次接触“卫星轨道分类”这个概念,很多人会觉得它离自己很远——不就是天上的星星怎么转吗?但如果你正在做航天任务规划、遥感数据接收、星座设计,甚至只是准备一场航天…

2026/10/10 0:00:39 阅读更多 →
Spring AOP 核心原理与实战:从概念到日志切面落地

Spring AOP 核心原理与实战:从概念到日志切面落地

1. 从一个真实痛点说起:为什么你的代码里到处都是重复逻辑刚入行那会儿,我写过一个用户管理模块,注册、登录、改密码、注销四个接口。每个接口里都塞了几乎一样的日志打印、参数校验、事务开启和提交。当时觉得没什么,能跑就行。直…

2026/10/10 0:00:40 阅读更多 →
Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

简介:这是一套面向计算机相关专业学生与项目实战学习者的Python数据采集与分析可视化完整项目,以Boss直聘岗位数据为对象,适合用作毕业设计、课程设计或期末大作业。资源包共38个文件,约246KB,以13个py源码文件为核心&…

2026/10/10 0:00:40 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 1:36:08 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 10:11:06 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 21:13:17 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 6:17:20 阅读更多 →