Python with 语句的作用是在语句块执行完毕后,自动调用 with 后面表达式的 __exit__ 方法。这允许我们不必显式地调用 close 方法,就可以确保资源总会被释放。with 语句可以使代码更清晰、更具可读性,简化了文件流、数据库连接等公共资源的管理。

使用 with 示例

一个最常见的例子是打开文件,使用 with 关键字,我们可以这么写:

1
2
with open('file.txt') as f:
read_data = f.read()

使用 with 关键字,我们不必显式调用 f.close() 来关闭文件。with 语句会在读取文件后自动关闭该文件。

如果我们不使用 with 关键字,为了避免在读取文件的过程中出现异常,使文件不能被正常关闭,导致文件资源一直被该程序占用而无法被释放,我们需要使用 try…except…finally 编码范式,具体代码如下:

1
2
3
4
5
6
7
try:
f = open('file.txt')
read_data = f.read()
except Exception as err:
print(err)
finally:
f.close()

在该代码示例中,我们对可能发生异常的代码处使用 try 关键字进行捕获,发生异常时执行 except 代码块,而 finally 代码块是无论什么情况都会执行,所以文件会被关闭,不会因为执行异常而导致该文件资源一直被占用。

通过对比这两个示例可以发现,使用 with 关键字的代码会更加简洁。

with 语句实现原理

with 语句对于任何实现了上下文管理协议(context management protocol)的对象都起作用。这个协议包含 __enter____exit__ 两个方法。__enter__ 方法在 with 块开始时被调用,并且返回一个用于该块的资源。__exit__ 方法在离开 with 块时,无论是正常退出还是由于异常,都会被调用。

例如,我们可以使用上下文管理器来实现一个简单的计时器(即 with 语句来记录代码块的执行时间),观察 with 语句的执行过程。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import time

class Timer:
def __enter__(self):
self.start = time.time()
print("__enter__: current_timestamp -", self.start)

def __exit__(self, exc_type, exc_val, exc_tb):
self.end = time.time()
print(self.end - self.start)
print("__exit__: current_timestamp -", self.end)

with Timer() as t:
print("with block, do something...")

"""
Output:
__enter__: current_timestamp - 1682600690.6863067
with block, do something...
0.0004775524139404297
__exit__: current_timestamp - 1682600690.6867843
"""

代码执行逻辑如下:

  1. 首先定义一个 Timer 类,它实现了 __enter____exit__ 方法,这使它可以作为上下文管理器使用。

  2. __enter__ 方法在 with 语句开始时被调用。它记录了当前时间,用于开始计时。

  3. with 语句块运行,执行需要计时的代码。

  4. __exit__ 方法在 with 语句结束时被调用。它再次记录当前时间,并计算执行时间(end - start),然后打印出来。

总体来说,这个 Timer 类通过 __enter____exit__ 两个方法来计算 with 代码块的执行时间。

(END)