Python基础之 上下文管理器|Python 主题月

585 阅读4分钟

1. 上下文管理器

本文正在参加「Python主题月」,详情查看活动链接

上下文管理器允许您在需要时准确地分配和释放资源。上下文管理器最广泛使用的例子是with语句。假设您有两个相关的操作,您希望它们成对执行,中间有一个代码块。上下文管理器可以帮助你专门执行此操作。例如:

with open('test_file', 'w') as s:
    s.write('hai!')

上面的代码打开文件,向其中写入一些数据,然后关闭它。如果在将数据写入文件时发生错误,它会尝试关闭它。上面的代码等价于:

file = open('test_file', 'w')
try:
    file.write('hai!')
finally:
    file.close()

将其与第一个示例进行比较时,我们可以看到仅使用with. 使用with语句的主要优点是它可以确保我们的文件关闭,而无需注意嵌套块的退出方式。

上下文管理器的一个常见用例是锁定和解锁资源以及关闭打开的文件(正如我已经向您展示的那样)。

让我们看看如何实现我们自己的上下文管理器。这应该能让我们准确地了解幕后发生的事情。

1.1 将上下文管理器实现为类

给上下文管理器定义了一个__enter__and __exit__方法。让我们制作我们自己的文件打开上下文管理器并学习基础知识。

class File(object):
    def __init__(self, file_name, method):
        self.file_obj = open(file_name, method)
    def __enter__(self):
        return self.file_obj
    def __exit__(self, type, value, traceback):
        self.file_obj.close()

只需通过定义__enter____exit__方法,我们就可以在with语句中使用我们的新类。我们试试看:

with File('text.txt', 'w') as s:
    s.write('hai!')

我们的__exit__方法接受三个参数。__exit__作为上下文管理器类的一部分的每个方法都需要它们。让我们谈谈幕后发生的事情。

  1. with语句存储 类的__exit__关闭方法。
  2. 它调用类的__enter__方法。
  3. __enter__方法打开文件并返回它。
  4. 打开的文件传递给s.
  5. 我们使用.write().
  6. with语句调用存储的__exit__关闭方法。
  7. 该调用__exit__方法关闭文件。

1.2 处理异常

我们没有讨论方法的type,valuetraceback 参数__exit__。在第 4 步和第 6 步之间,如果发生异常,Python 会将异常的类型、值和回溯传递给__exit__方法。它允许__exit__方法决定如何关闭文件以及是否需要任何进一步的步骤。在我们的例子中,我们没有关注它们。

如果我们的文件对象引发异常怎么办?我们可能正在尝试访问它不支持的文件对象上的方法。例如:

with File('test.txt', 'w') as s:
    s.undefined_function('hai!')

让我们列出with遇到错误时语句所采取的步骤:

  1. 它将错误的类型、值和回溯传递给 __exit__方法。
  2. 它允许__exit__方法处理异常。
  3. 如果__exit__返回,True则异常得到妥善处理。
  4. 如果方法True不返回任何其他内容(None),则__exit__with语句引发异常。

在我们的例子中,__exit__方法返回None(当没有遇到 return 语句时,方法返回None)。因此,该with语句引发了异常:

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
AttributeError: 'file' object has no attribute 'undefined_function'

让我们尝试处理__exit__方法中的异常:

class File(object):
    def __init__(self, file_name, method):
        self.file_obj = open(file_name, method)
    def __enter__(self):
        return self.file_obj
    def __exit__(self, type, value, traceback):
        print("Exception has been handled")
        self.file_obj.close()
        return True

with File('demo.txt', 'w') as opened_file:
    opened_file.undefined_function()

# Output: Exception has been handled

我们的__exit__方法返回了True,因此with语句没有引发异常。

这不是实现上下文管理器的唯一方法。还有另一种方式,我们将在下一节中讨论。

1.3 将上下文管理器实现为生成器

我们还可以使用装饰器和生成器来实现上下文管理器。为此,Python 有一个 contextlib 模块。我们可以使用生成器函数来实现上下文管理器,而不是类。让我们看一个基本的例子:

from contextlib import contextmanager

@contextmanager
def open_file(name):
    f = open(name, 'w')
    try:
        yield f
    finally:
        f.close()

好的!这种实现上下文管理器的方式似乎更加直观和简单。然而,这种方法需要一些关于生成器、产量和装饰器的知识。在这个例子中,我们没有捕捉到任何可能发生的异常。它的工作方式与前一种方法大致相同。

让我们稍微剖析一下这个方法。

  1. Python 遇到yield关键字。因此,它创建了一个生成器而不是一个普通的函数。
  2. 由于装饰,上下文管理器以函数名 ( open_file) 作为参数被调用。
  3. contextmanager装饰返回由包裹发生器 GeneratorContextManager对象。
  4. GeneratorContextManager分配给open_file 功能。因此,当我们稍后调用该open_file函数时,我们实际上是在调用该GeneratorContextManager对象。

所以现在我们知道了这一切,我们可以像这样使用新生成的上下文管理器:

with open_file('some_file') as f:
    f.write('hai!')