Python 上下文管理器

PythonBeginner
立即练习

介绍

尽管 Python 的上下文管理器(context managers)被广泛使用,但很少有人真正理解其背后的目的。这些语句通常用于文件的读写操作,它们通过确保特定资源仅在特定进程中使用,来帮助应用程序节约系统内存并改进资源管理。

在这个 Lab 中,你将学习如何使用 with 语句进行自动资源管理,然后你将使用两种不同的方法创建你自己的上下文管理器:一种是基于函数的(function-based)方法,另一种是基于类的(class-based)方法。

with 语句

在这一步,你将学习如何将 with 语句与上下文管理器(context managers),如文件对象一起使用。上下文管理器是一种对象,它会在一个上下文(一段代码块)开始和结束时收到通知。with 语句会自动处理这个通知过程。

例如,文件对象就是上下文管理器。当一个 with 代码块结束时,文件对象会自动关闭,即使代码块中发生了错误。这可以防止数据丢失和资源泄漏。

注意: 如果你不熟悉使用 WebIDE(类似 VS Code),请先在 这个实验 学习。

让我们实际看看它是如何工作的。在左侧的文件浏览器中,打开 main.py 文件。将以下代码复制并粘贴到其中。这段代码将首先使用 with 语句创建并写入一个文件,然后读取其内容。

## 我们将创建一个文件来演示 'with' 语句。
## 首先,让我们向文件中写入一些内容。
with open('my_file.txt', 'w') as f:
    f.write('This is a test file.')

## 现在,让我们使用上下文管理器将其读回。
## 退出 'with' 代码块时,文件会自动关闭。
with open('my_file.txt') as f:
    file_contents = f.read()
    print(file_contents)

## 即使发生错误,文件也会在这里自动关闭。
print("\nFile has been read and closed.")

现在,从终端运行脚本以查看输出。

python3 main.py

你应该会看到以下输出,确认文件已成功写入和读取。with 语句确保在写入和读取操作之后,my_file.txt 都被正确关闭。

This is a test file.

File has been read and closed.

使用 with 语句是处理文件或网络连接等资源的推荐方式,因为它能使你的代码更简洁、更安全。

编写你自己的上下文管理器

在这一步,你将发现如何使用生成器函数(generator function)和 @contextlib.contextmanager 装饰器来创建自定义的上下文管理器。这是一种创建简单上下文管理器的简洁方法。

yield 语句之前的代码在进入 with 代码块时运行(设置逻辑),yield 语句之后的代码在退出时运行(清理逻辑)。通过在 with 语句中使用 as 关键字,yield 的值可以被赋给一个变量。

从文件浏览器中打开 function_cm.py 文件。将以下代码复制到文件中。

## 使用 contextlib 装饰器的基于函数的上下文管理器
import contextlib

@contextlib.contextmanager
def context_manager(num):
    print('Enter')  ## yield 之前的代码在 __enter__ 时运行
    yield num + 1   ## yield 的值成为 'cm' 变量
    print('Exit')    ## yield 之后的代码在 __exit__ 时运行

with context_manager(2) as cm:  ## cm 接收 yield 的值 (3)
    print('Right in the middle with cm = {}'.format(cm))

现在,在终端中执行脚本。

python3 function_cm.py

你将看到以下输出。注意执行的顺序:yield 之前的代码首先运行,然后是 with 代码块内部的代码,最后是 yield 之后的代码。

Enter
Right in the middle with cm = 3
Exit

这种方法对于创建临时上下文非常有用,例如设置和拆除数据库连接或临时更改某个设置。

基于类的上下文管理器

在这一步,你将学习如何将上下文管理器实现为一个类。这种方法提供了更多的控制权,适用于更复杂的场景。要创建一个基于类的上下文管理器,你必须定义两个特殊方法:__enter__ 和 __exit__。

  • __enter__: 当进入 with 语句时调用此方法。它应该执行设置(setup)逻辑。如果提供了 as 关键字,它的返回值将被赋给 as 后面的变量。
  • __exit__: 当 with 代码块退出时(无论是正常退出还是由于异常退出),都会调用此方法。它应该执行清理(cleanup)逻辑。

让我们创建一个。打开 class_cm.py 文件并将以下代码复制进去。

## 基于类的上下文管理器:实现 __enter__ 和 __exit__ 方法
class ContextManager:
    def __enter__(self, *args, **kwargs):  ## 进入 'with' 代码块时调用
        print("--enter--")
        return self  ## 可以返回对象用作 'as' 变量

    def __exit__(self, *args):  ## 退出 'with' 代码块时调用
        print("--exit--")

with ContextManager():  ## 调用 __enter__,完成后调用 __exit__
    print("test")

从终端运行脚本。

python3 class_cm.py

输出展示了控制流。首先调用 __enter__ 方法,然后是 with 代码块内的代码,最后调用 __exit__ 方法进行清理。

--enter--
test
--exit--

基于类的方法非常强大,因为它允许你在一个可重用的对象中封装复杂的设置和拆卸(teardown)逻辑。

总结

恭喜你完成了本次实验(Lab)!你已经学会了如何使用上下文管理器在 Python 中有效地管理资源。

你从用于安全文件处理的常见 with 语句开始。然后,你学习了两种不同的方法来创建自己的自定义上下文管理器:

  • 使用 @contextlib.contextmanager 装饰器的基于函数的方法,它非常适合简单的设置和拆卸任务。
  • 通过实现 __enter__ 和 __exit__ 方法的基于类的方法,这非常适合更复杂的资源管理。

掌握这些技能对于编写能够正确处理资源的、干净、可靠且高效的 Python 代码至关重要。