with 语句与上下文管理器
with 语句是 try-finally 的语法糖,用于管理资源的获取和释放。它确保代码块执行完毕后,资源被正确清理——无论代码块是正常结束还是因异常退出。
文件操作
with open("data.txt", "r") as f:
data = f.read()
print data
# f 已经自动关闭
print f.closed # True
with 语句的执行过程:
- 调用
open("data.txt", "r"),得到文件对象 - 调用文件对象的
__enter__()方法,返回值赋给f - 执行缩进块中的代码
- 调用文件对象的
__exit__()方法,关闭文件
即使缩进块中发生异常,__exit__() 仍然会被调用,文件一定被关闭。
与 try-finally 的对比
# try-finally 写法
f = open("data.txt", "r")
try:
data = f.read()
finally:
f.close()
# with 写法(等价但更简洁)
with open("data.txt", "r") as f:
data = f.read()
with 不仅更简洁,而且把"资源管理"和"业务逻辑"分离,代码更清晰。
多个上下文管理器
Python 2.7+ 支持一个 with 语句管理多个资源:
with open("input.txt", "r") as src, open("output.txt", "w") as dst:
dst.write(src.read())
Python 2.6 及更早版本需要用嵌套:
with open("input.txt", "r") as src:
with open("output.txt", "w") as dst:
dst.write(src.read())
自定义上下文管理器
任何实现了 __enter__() 和 __exit__() 方法的对象都可以用于 with 语句:
class ManagedFile(object):
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.file = None
def __enter__(self):
self.file = open(self.filename, self.mode)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
if self.file:
self.file.close()
return False # 不抑制异常
with ManagedFile("data.txt", "r") as f:
data = f.read()
__exit__() 的三个参数:
exc_type:异常类型(无异常时为None)exc_val:异常值exc_tb:异常追踪信息
返回 True 表示抑制异常(不向上传播),返回 False 表示正常抛出异常。
使用 contextlib 简化
contextlib 模块提供了装饰器,可以用生成器简化上下文管理器的编写:
from contextlib import contextmanager
@contextmanager
def managed_file(filename, mode):
f = open(filename, mode)
try:
yield f # yield 之前的代码是 __enter__,之后是 __exit__
finally:
f.close()
with managed_file("data.txt", "r") as f:
data = f.read()
yield 之前的代码在 with 块开始时执行,yield 返回的值赋给 as 后的变量。with 块结束后,执行 yield 之后的代码。
实际应用
数据库连接:
@contextmanager
def db_connection():
conn = create_connection()
try:
yield conn
finally:
conn.close()
with db_connection() as conn:
cursor = conn.cursor()
cursor.execute("SELECT * FROM users")
临时修改环境:
@contextmanager
def temp_directory(path):
old_dir = os.getcwd()
os.chdir(path)
try:
yield
finally:
os.chdir(old_dir)
with temp_directory("/tmp"):
# 在这个块中,当前目录是 /tmp
print os.getcwd() # /tmp
print os.getcwd() # 恢复为原来的目录
锁管理:
import threading
lock = threading.Lock()
with lock:
# 临界区,自动获取和释放锁
shared_data += 1
异常处理
with 块中的异常会被 __exit__() 接收:
class SuppressZeroDivision(object):
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is ZeroDivisionError:
print "ZeroDivisionError suppressed"
return True # 抑制异常
return False
with SuppressZeroDivision():
print 1 / 0 # 输出 ZeroDivisionError suppressed,程序继续
print "Still running" # 执行
这种设计让上下文管理器可以智能地处理特定异常,比如数据库事务中的回滚逻辑。
上下文管理器的嵌套
with database_connection() as db:
with db.transaction() as tx:
with open("data.json", "r") as f:
data = json.load(f)
tx.insert(data)
每个 with 管理自己的资源,异常时按相反的顺序清理。