Python 上下文管理器与文件操作:资源安全管理本文适合谁:了解 Java try-with-resources 和 AutoCloseable 的工程师,想理解 Pythonwith语句和上下文管理器的工作原理。读完本篇,你能安全管理 AI 项目中的资源:HTTP 连接、临时文件、数据库连接。文件句柄(打开文件后系统返回的操作凭证)、数据库连接、网络套接字(网络通信的端点)——这类资源有一个共同特点:用完必须释放。忘记关闭文件会导致文件描述符泄漏(系统能同时打开的文件数有上限);忘记关闭数据库连接会耗尽连接池(预先建立的一批数据库连接,供多个请求复用);在 LLM API 客户端的生命周期没有正确管理时,底层的 HTTP 连接池会持续占用。Python 的上下文管理器(Context Manager)提供了一个统一的模式来解决这个问题:用with语句声明资源的生命周期,确保无论是否发生异常,退出时都会执行清理代码。1.1 with 语句的底层原理enter到exit的完整生命周期,包含异常处理路径with语句背后是两个方法:__enter__和__exit__。# with 语句的语法糖展开:withopen("data.txt")asf:content=f.read()# 等价于:f=open("data.txt")f.__enter__()# 进入上下文,通常返回资源对象本身try:content=f.read()finally:f.__exit__(None,None,None)# 无论是否异常,都会执行__exit__的三个参数是异常信息:exc_type(异常类型)、exc_val(异常值)、exc_tb(调用栈)。没有异常时三个都是None;返回True表示异常已处理,返回False或None表示让异常继续向上传播。手动实现一个上下文管理器,直观理解执行顺序:classTimer:"""计时器上下文管理器:测量代码块耗时"""importtimedef__init__(self,label:str):self.label=label self.elapsed=0.0def__enter__(self):self._start=self.time.monotonic()returnself# 返回 self,这样 'as timer' 拿到的是 Timer 实例def__exit__(self,exc_type,exc_val,exc_tb):self.elapsed=self.time.monotonic()-self._startprint(f"{self.label}:{self.elapsed:.3f}s")returnFalse# 不压制异常,异常会继续传播withTimer("数据处理")ast:data=list(range(1_000_000))total=sum(data)print(f"耗时{t.elapsed:.3f}s,结果:{total}")# 输出:数据处理: 0.045s# 耗时 0.045s,结果:4999995000001.2 contextlib.contextmanager:用生成器写上下文管理器手动写__enter__/__exit__需要一个专门的类,代码量不少。contextlib.contextmanager装饰器允许用一个生成器函数来定义上下文管理器:fromcontextlibimportcontextmanagerimporttempfile,shutil,osfrompathlibimportPath@contextmanagerdeftemp_directory(prefix:str="ai_work_"):""" 创建临时工作目录,退出时自动清理 yield 之前的代码 = __enter__ yield 之后的代码 = __exit__ """tmpdir=tempfile.mkdtemp(prefix=prefix)try:yieldPath(tmpdir)# 把临时目录路径交给 with 块使用finally:# 无论 with 块里发生什么,都会清理临时目录shutil.rmtree(tmpdir,ignore_errors=True)# 使用示例:下载模型文件到临时目录,处理完自动删除withtemp_directory("model_download_")aswork_dir:output_file=work_dir/"result.json"output_file.write_text('{"status": "done"}')print(f"临时目录:{work_dir}")# 做完工作后,with 块退出,临时目录被自动删除# 此时 work_dir 已不存在print(work_dir.exists())# Falseyield前后的代码对应__enter__和__exit__,try/finally保证清理代码必然执行。这个模式比写类简洁很多。1.3 AI 开发中的典型场景1.3.1 临时目录管理AI 推理任务经常需要临时文件:缓存中间结果、保存临时图片、存放模型下载的权重文件。临时目录用完就删,是干净的工程实践:fromcontextlibimportcontextmanagerimporttempfile,shutilfrompathlibimportPath@contextmanagerdefinference_workspace(model_name:str):""" 为单次推理任务创建独立的工作空间 多个并发推理任务不会互相干扰 """workspace=Path(tempfile.mkdtemp(prefix=f"{model_name}_"))(workspace/"inputs").mkdir()(workspace/"outputs").mkdir()try:yieldworkspacefinally:shutil.rmtree(workspace,ignore_errors=True)withinference_workspace("stable_diffusion")asws:input_path=ws/"inputs"/"prompt.txt"output_path=ws/"outputs"/"image.png"input_path.write_text("a cat on the moon")# run_inference(input_path, output_path)print(f"输出:{output_path}")1.3.2 数据库连接池fromcontextlibimportcontextmanagerfromtypingimportGeneratorimportsqlite3classDatabasePool:"""简化的连接池,演示上下文管理器在数据库场景的应用"""