Python中的 with 语句
Python中的with
语句用于异常处理,以使代码更简洁、更具可读性。它简化了文件流等公共资源的管理。观察以下代码示例,了解with
语句的使用如何使代码更简洁。
# file handling
# 1) without using with statement
file = open('file_path', 'w')
file.write('hello world !')
file.close()
# 2) without using with statement
file = open('file_path', 'w')
try:
file.write('hello world')
finally:
file.close()
# using with statement
with open('file_path', 'w') as file:
file.write('hello world !')
请注意,与前两个实现不同,使用with
语句时无需调用file.close()
。 with
语句本身确保正确获取和释放资源。在第一个实现中的file.write()
调用期间的异常会阻止文件正确关闭,这可能会在代码中引入一些错误,即文件中的许多更改在文件正确关闭之前不会生效。
上面示例中的第二种方法处理了所有异常,但使用with
语句使代码更紧凑且更具可读性。因此, with
语句通过确保在使用资源的代码完全执行时正确释放资源来帮助避免错误和泄漏。 with
语句通常用于文件流,如上所示,以及锁、套接字、子进程和远程登录等。
支持用户定义对象中的“with”语句
open()
没有什么特别之处,它可以与with
语句一起使用,并且可以在用户定义的对象中提供相同的功能。在您的对象中支持with
语句将确保您永远不会打开任何资源。
要在用户定义的对象中使用with
语句,您只需在对象方法中添加方法 __enter__( __enter__()
和__exit__()
。请考虑以下示例以进行进一步说明。
# a simple file writer object
class MessageWriter(object):
def __init__(self, file_name):
self.file_name = file_name
def __enter__(self):
self.file = open(self.file_name, 'w')
return self.file
def __exit__(self):
self.file.close()
# using with statement with MessageWriter
with MessageWriter('my_file.txt') as xfile:
xfile.write('hello world')
让我们检查一下上面的代码。如果您注意到, with
关键字后面是MessageWriter
的构造函数。一旦执行进入with
语句的上下文,就会创建一个MessageWriter
对象,然后Python调用__enter__()
方法。在这个__enter__()
方法中,初始化您希望在对象中使用的资源。此__enter__()
方法应始终返回获取资源的描述符。
什么是资源描述符?
这些是操作系统提供的用于访问所请求资源的句柄。在下面的代码块中, file
是文件流资源的描述符。
file = open('hello.txt')
在上面提供的MessageWriter
示例中, __enter__()
方法创建一个文件描述符并返回它。这里的名称xfile
用于引用__enter__()
方法返回的文件描述符。使用获取的资源的代码块放置在with
语句的块中。一旦执行with
块中的代码,就会调用__exit__()
方法。所有获取的资源都在__exit__()
方法中释放。这就是我们如何将with
语句与用户定义的对象一起使用。
__enter__ __enter__()
和__exit__()
方法的接口提供了对用户定义对象中with
语句的支持,称为Context Manager 。
上下文库模块
如上所示的基于类的上下文管理器并不是支持用户定义对象中的with
语句的唯一方法。 contextlib
模块提供了更多基于基本上下文管理器接口的抽象。下面是我们如何使用 contextlib 模块重写MessageWriter
对象的contextlib
管理器。
from contextlib import contextmanager
class MessageWriter(object):
def __init__(self, filename):
self.file_name = filename
@contextmanager
def open_file(self):
try:
file = open(self.file_name, 'w')
yield file
finally:
file.close()
# usage
message_writer = MessageWriter('hello.txt')
with message_writer.open_file() as my_file:
my_file.write('hello world')
在这个代码示例中,由于其定义中的yield
语句,函数open_file()
是一个生成器函数。
当调用这个open_file()
函数时,它会创建一个名为file
的资源描述符。然后将此资源描述符传递给调用者,并在此处由变量my_file
表示。在with
块内的代码执行后,程序控制返回到open_file()
函数。 open_file()
函数继续执行并执行yield
语句之后的代码。在yield
语句之后出现的这部分代码释放了获取的资源。这里的@contextmanager
是一个装饰器。
先前基于类的实现和这个基于生成器的上下文管理器实现在内部是相同的。虽然后者看起来更具可读性,但它需要生成器、装饰器和yield
的知识。