在 Python 编程中,资源管理是一个重要但容易被忽视的环节。 with 关键字为我们提供了一种优雅的方式来处理文件操作、数据库连接等需要明确释放资源的场景。
with 是 Python 中的一个关键字,用于上下文管理协议(Context Management Protocol)。它简化了资源管理代码,特别是那些需要明确释放或清理的资源(如文件、网络连接、数据库连接等)。
为什么需要 with 语句
传统资源管理的问题
我们先看一个典型的文件操作示例:
实例
file = open('example.txt', 'r')
try:
content = file.read()
# 处理文件内容
finally:
file.close()这种写法存在几个问题:
- 容易忘记关闭资源 :如果没有
try-finally块,可能会忘记调用close() - 代码冗长 :简单的文件操作需要多行代码
- 异常处理复杂 :需要手动处理可能出现的异常
with 语句的优势
with 语句通过上下文管理协议(Context Management Protocol)解决了这些问题:
- 自动资源释放 :确保资源在使用后被正确关闭
- 代码简洁 :减少样板代码
- 异常安全 :即使在代码块中发生异常,资源也会被正确释放
- 可读性强 :明确标识资源的作用域
with 语句的基本语法
基础用法
with 语句的基本形式如下:
语法格式
with expression [as variable]:
# 代码块expression返回一个支持上下文管理协议的对象as variable是可选的,用于将表达式结果赋值给变量- 代码块执行完毕后,自动调用清理方法
文件操作示例
最常见的 with 语句应用是文件操作:
实例
with open('example.txt', 'r') as file:
content = file.read()
print(content)
# 文件已自动关闭这段代码等价于前面的 try-finally 实现,但更加简洁明了。
with 语句的工作原理
上下文管理协议
with 语句背后是 Python 的上下文管理协议,该协议要求对象实现两个方法:
__enter__():进入上下文时调用,返回值赋给as后的变量__exit__():退出上下文时调用,处理清理工作
执行流程

异常处理机制
__exit__() 方法接收三个参数:
exc_type:异常类型exc_val:异常值exc_tb:异常追踪信息
如果 __exit__() 返回 True ,则表示异常已被处理,不会继续传播;返回 False 或 None ,异常会继续向外传播。
实际应用场景
1. 文件操作
实例
# 同时打开多个文件
with open('input.txt', 'r') as infile, open('output.txt', 'w') as outfile:
content = infile.read()
outfile.write(content.upper())2. 数据库连接
实例
import sqlite3
with sqlite3.connect('database.db') as conn:
cursor = conn.cursor()
cursor.execute('SELECT * FROM users')
results = cursor.fetchall()
# 连接自动关闭3. 线程锁
实例
import threading
lock = threading.Lock()
with lock:
# 临界区代码
print("这段代码是线程安全的")4. 临时修改系统状态
实例
import decimal
with decimal.localcontext() as ctx:
ctx.prec = 42 # 临时设置高精度
# 执行高精度计算
# 精度恢复原设置创建自定义的上下文管理器
类实现方式
我们可以通过实现 __enter__ 和 __exit__ 方法创建自定义的上下文管理器:
实例
class Timer:
def __enter__(self):
import time
self.start = time.time()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
import time
self.end = time.time()
print(f"耗时: {self.end - self.start:.2f}秒")
return False
# 使用示例
with Timer() as t:
# 执行一些耗时操作
sum(range(1000000))使用 contextlib 模块
Python 的 contextlib 模块提供了更简单的方式来创建上下文管理器:
实例
from contextlib import contextmanager
@contextmanager
def tag(name):
print(f"<{name}>")
yield
print(f"</{name}>")
# 使用示例
with tag("h1"):
print("这是一个标题")输出:
<h1>
这是一个标题
</h1>常见问题与最佳实践
常见错误
1、错误地认为 with 只能用于文件 :
实例
# 错误:认为只有文件需要with
conn = sqlite3.connect('db.sqlite')
# 应该使用with语句2、忽略__exit__的返回值 :
实例
class MyContext:
def __exit__(self, exc_type, exc_val, exc_tb):
# 忘记返回True/False可能导致异常处理不符合预期
pass最佳实践
- 优先使用 with 管理资源 :对于文件、网络连接、锁等资源,总是优先考虑使用
with语句 - 保持上下文简洁 :
with块中的代码应该只包含与资源相关的操作 - 合理处理异常 :在自定义上下文管理器中,根据需求决定是否抑制异常
- 利用多个上下文 :Python 允许在单个
with语句中管理多个资源
总结要点
| 关键点 | 说明 |
|---|---|
| 自动资源管理 | with 语句确保资源被正确释放 |
| 上下文协议 | 需要实现 __enter__ 和 __exit__ 方法 |
| 异常安全 | 即使代码块中出现异常,资源也会被释放 |
| 常见应用 | 文件操作、数据库连接、线程锁等 |
| 自定义实现 | 可以通过类或 contextlib 创建自定义上下文管理器 |
with 语句是 Python 中一项强大的特性,它不仅能简化代码,还能提高程序的健壮性。掌握 with 语句的使用和原理,将帮助你写出更专业、更可靠的 Python 代码。
相关
练习题
一、知识回顾(读完直接做下面的实践题)
with语句用来做上下文管理:代码块执行前后自动完成"准备/清理"动作,最典型的就是文件用完自动关闭- 基本语法:
with 表达式 as 变量:加缩进的代码块;as 变量可以省略(如直接with lock:) - 优点:自动释放资源、代码更短、中途出异常也能正确清理、资源的作用域一目了然(它等价于 try-finally,但更简洁)
- 背后是上下文管理协议:对象要实现
__enter__()(进入时调用,返回值赋给as后的变量)和__exit__()(退出时调用,负责清理) __exit__()接收三个参数:exc_type(异常类型)、exc_val(异常值)、exc_tb(追踪信息);它返回True表示异常已被处理、不再往外传,返回False或None则异常继续向外传播- 文件操作是最常见的用法:
with open(文件名, 模式) as f:——写入用'w'模式、读取用'r'模式,出了 with 代码块文件就已自动关闭 - 一个
with可以同时管理多个资源,用逗号隔开:with open('input.txt', 'r') as infile, open('output.txt', 'w') as outfile: - 其它应用场景:数据库连接(
sqlite3.connect())、线程锁(threading.Lock())、临时修改系统状态(decimal.localcontext()) - 自定义上下文管理器两种路子:类实现
__enter__/__exit__;或者用contextlib的@contextmanager装饰一个带yield的函数(yield 前是准备、yield 后是清理) - 常见错误与最佳实践:别以为 with 只能开文件;自定义时别忘了按需返回
True/False;管资源优先用 with,且 with 块里只放与资源相关的操作
二、裸写题
- 用
with语句读取文件内容 - 用
with语句写入文件
批改(2026-09-28):✅ 正确(先写入
text.txt再读出,输出Hello Python!,文件内容与写入值一致)。- 提示(先自己想,实在想不出再点开)
一级 · 思路:两段独立的 with:一段负责写、一段负责读;每段结束文件都会自动关掉,不用手动关 二级 · 方法:
with open('文件名', 'w') as f:写入用f.write("内容");with open('文件名', 'r') as f:读取用f.read()三级 · 骨架:with open('test.txt', '____') as f:/f.____("Hello, Python!")/content = f.____()- 参考答案(做完再点开)
python# 2-1 # 写入文件 with open('test.txt', 'w') as f: f.write("Hello, Python!") # 读取文件 with open('test.txt', 'r') as f: content = f.read() print(content)- 用
- 用
with同时打开两个文件 - 从一个文件读取内容,写入另一个文件
批改(2026-09-28):⚠️ 复制功能正常(
dest.txt内容为你好),但有调试残留print(f2),把文件对象打成了<_io.TextIOWrapper name='dest.txt' ...>;这一行应删掉(或改成print("复制完成"))。- 提示(先自己想,实在想不出再点开)
一级 · 思路:一个 with 里用逗号挂两个文件对象,一个只读、一个只写;先从读的对象取内容,再交给写的对象 二级 · 方法:
with open('源', 'r') as infile, open('目标', 'w') as outfile:;读infile.read()、写outfile.write(内容)三级 · 骨架:with open('source.txt', 'r') as ____, open('dest.txt', 'w') as ____:/content = infile.____()/outfile.____(content)- 参考答案(做完再点开)
python# 2-2 # 先创建源文件 with open('source.txt', 'w') as f: f.write("这是源文件内容") # 同时读取和写入 with open('source.txt', 'r') as infile, open('dest.txt', 'w') as outfile: content = infile.read() outfile.write(content.upper()) print("复制完成")- 用
- 用类实现一个计时器上下文管理器
- 记录代码执行时间
- 提示(先自己想,实在想不出再点开)
一级 · 思路:类就像个"开关":进入时记下开始时间,退出时再记一次、把差值打印出来 二级 · 方法:类里实现
__enter__(self)(进入时调用,记录开始时间并return self)和__exit__(self, exc_type, exc_val, exc_tb)(退出时调用,算耗时并打印、return False);取时间用time.time()三级 · 骨架:class Timer:/def ____(self):/self.start = time.time()/return ____/def ____(self, exc_type, exc_val, exc_tb):/print(f"耗时: {self.end - self.start:.2f}秒")- 参考答案(做完再点开)
python# 2-3 class Timer: def __enter__(self): import time self.start = time.time() return self def __exit__(self, exc_type, exc_val, exc_tb): import time self.end = time.time() print(f"耗时: {self.end - self.start:.2f}秒") return False # 使用示例 with Timer(): total = sum(range(1000000)) print(f"计算结果: {total}")- 用函数配合装饰器的方式创建上下文管理器(不写类,借助标准库现成的工具)
- 实现一个简单的标签生成器
- 提示(先自己想,实在想不出再点开)
一级 · 思路:这次不写类,改成写一个函数:
yield之前的代码在进入时执行(打印开标签),yield之后的代码在退出时执行(打印闭标签) 二级 · 方法:from contextlib import contextmanager导入装饰器,用@contextmanager装饰带yield的函数 三级 · 骨架:from contextlib import ____/@____/def tag(name):/print(f"<{name}>")/____/print(f"</{name}>")- 参考答案(做完再点开)
python# 2-4 from contextlib import contextmanager @contextmanager def tag(name): print(f"<{name}>") yield print(f"</{name}>") # 使用示例 with tag("h1"): print("这是一个标题") with tag("p"): print("这是一个段落")
