📜  Python中的 with 语句

📅  最后修改于: 2022-05-13 01:54:45.291000             🧑  作者: Mango

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的知识。