Python 的 with
语句可以用来管理资源的自动清理,并替代 try...finally
语句,使代码更简洁易读。
1. with
语句的作用
在 Python 里,with
语句通常用于管理资源,比如文件、数据库连接、网络请求等。
它可以保证无论代码是否执行成功,都会执行必要的清理工作,比如自动关闭文件、断开连接等。
示例: 用 with
语句打开文件,自动关闭:
with open('foo.txt') as fp:content = fp.read() # 读取文件内容
# 代码运行到这里,文件会自动关闭
在没有 with
的情况下,必须手动关闭文件:
fp = open('foo.txt')
try:content = fp.read()
finally:fp.close() # 确保文件被关闭
对比可见,with
语句更简洁,不用显式调用 close()
。
2. with
的底层原理——上下文管理器
with
之所以能自动管理资源,是因为它依赖上下文管理器(context manager)。
上下文管理器是实现了 __enter__
和 __exit__
方法的对象。
__enter__()
: 进入with
代码块时调用,可以返回一个资源对象。__exit__()
: 退出with
代码块时调用,处理异常并进行清理工作。
示例:手写一个上下文管理器
import randomclass DummyContext:def __init__(self, name):self.name = namedef __enter__(self):"""进入上下文管理器,返回带随机后缀的 name"""return f'{self.name}-{random.random()}'def __exit__(self, exc_type, exc_val, exc_tb):"""退出上下文管理器时,执行清理操作"""print('Exiting DummyContext')with DummyContext('foo') as name:print(f'Name: {name}')
执行结果:
Name: foo-0.123456789
Exiting DummyContext
可以看到 __exit__
方法无论有没有异常,都会在退出时执行。
3. with
语句的实际用途
(1)替代 try...finally
进行资源管理
在管理网络连接、数据库连接等资源时,通常会使用 try...finally
语句来确保即使发生异常,资源也会被正确释放。
但 with
语句可以让代码更简洁。
传统 try...finally
方式:
conn = create_conn(host, port)
try:conn.send_text('Hello, world!')
except Exception as e:print(f'Unable to use connection: {e}')
finally:conn.close() # 确保连接被关闭
使用 with
语句简化:
class create_conn_obj:"""创建连接对象,并在退出上下文时自动关闭"""def __init__(self, host, port):self.conn = create_conn(host, port)def __enter__(self):return self.conndef __exit__(self, exc_type, exc_value, traceback):self.conn.close() # 退出时自动关闭return False # 让异常继续传播(如果发生异常)with create_conn_obj(host, port) as conn:conn.send_text('Hello, world!')
在 with
代码块结束时,无论是否有异常,连接都会自动关闭。
(2)用于忽略特定异常
有时候,我们可能想忽略某些特定的异常,让代码继续执行。例如:
try:close_conn(conn)
except AlreadyClosedError:pass # 忽略异常
这种写法虽然简单,但会导致 try...except
语句分散在代码各处,不易维护。
可以使用 上下文管理器 来封装忽略异常的逻辑:
class ignore_closed:"""忽略 AlreadyClosedError 异常"""def __enter__(self):passdef __exit__(self, exc_type, exc_value, traceback):if exc_type == AlreadyClosedError:return True # 返回 True,表示异常已被处理,不再传播return False # 其他异常继续传播with ignore_closed():close_conn(conn) # 即使连接已关闭,也不会抛出错误
如果 close_conn(conn)
触发 AlreadyClosedError
,它会被 ignore_closed
处理掉,而不会终止程序。
实际上,Python 标准库 contextlib
提供了 suppress()
方法,可以直接用:
from contextlib import suppresswith suppress(AlreadyClosedError):close_conn(conn) # 忽略异常
这样代码更加简洁。
4. contextmanager
装饰器简化上下文管理器
如果只是为了实现简单的上下文管理器,不必写一个完整的类,可以使用 contextlib.contextmanager
装饰器,用函数+yield
来实现。
示例:
from contextlib import contextmanager@contextmanager
def create_conn_obj(host, port):"""创建连接对象,并在退出上下文时自动关闭"""conn = create_conn(host, port)try:yield conn # 这里返回 conn,类似于 __enter__finally:conn.close() # 退出时关闭连接,类似于 __exit__with create_conn_obj(host, port) as conn:conn.send_text('Hello, world!')
在 yield
之前的代码在进入 with
代码块时执行(类似 __enter__
),
yield
之后的代码在退出 with
代码块时执行(类似 __exit__
)。
相比手写类,这个方法更加简洁。
总结
with
语句用于自动管理资源,替代try...finally
,代码更简洁。- 上下文管理器(
__enter__
和__exit__
)可以实现with
语句的功能,比如自动关闭文件、数据库连接、网络请求。 with
可以用于异常处理,例如:- 替代
try...finally
进行资源清理 - 忽略特定异常
- 替代
@contextmanager
装饰器可以简化上下文管理器,让代码更优雅。