Python上下文管理器

Python 上下文管理器

大家都用过Python中的with语句,使用with语句其实就是一个上下文管理器,那么他主要做了一个在执行某个动作前做了一件事情,做完了这个事情之后又做了另外一件事情。虽然听起来比较难懂,但是大体意思是这样的,那么我们就一起用代码来看看是怎么回事吧!

代码解释

1
2
3
info = open("mydata.txt", "w")
info.write("hello welcome to my blog")
info.close()

上面就是实现了一个简单的打开mydata.txt文件,然后写入字符,然后关闭。这样的一个操作过程。

那么使用with 改造后呢?

1
2
with open("mydata.txt", "w") as info:
info.write("welocme to my blog")

那么当我们使用with上下文管理器的时候,就是这样的了,可能同学会发现咦,怎么没有关闭文件了,这个呢就是上下文管理器的神奇之处,他会在写完数据后自动执行关闭的操作,是不是比我们自己写的要更加简单以及安全!那么执行完毕后就会生成mydata.txt文件。

那么我们重新来看一个程序!

上下文管理器内部逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import time

start=time.perf_counter()
nums = []

for n in range(1000):
nums.append(n**5)

stop= time.perf_counter()

cost = stop - start

print(cost)

那么上面的代码中简单的实现了一个这样的逻辑,就是实现计算n的5次方,然后计算从0到1000,然后加入列表中,开始计时查看中间的时间差。这时候,我们通过写内部真正实际操作的逻辑!

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 __init__(self):
self.cost = 0

def __enter__(self):
self.start=time.perf_counter()
return self

def __exit__(self, exc_type, exc_val, exc_tb):
self.stop = time.perf_counter()
self.cost = self.stop - self.start



with Timer() as timer:
nums = []
for n in range(1000):
nums.append(n**5)
print(timer.cost)

那么这时候可以看出来,我们写了一个with的上下文管理器,当执行的时候首先进去Timer这个类里面,需要注意的是,这时候首先执行的是 __enter__ 这个方法,然后当我们整个with结束的时候执行的是 __exit__ 这个方法,此时在__exit__中我们做了记录结束的时间并且做了两者相减的时间差的值,这样我们就可以在打印timer.cost的时候获取时间差。

需要注意的是在__enter__ 这个里面有一个return self。是因为我们在执行with Timer() as timer的时候 timer 并不是真正的拿到Timer(), 其实是拿到的是__enter__这里的返回信息,也就是self.start 此时需要用retrun 将其返回记录的!

代码内的总结

一个上下文管理器需要实现如下的方法:

__enter__ 安装上下文,可以返回对象

__exit__ 清楚释放对象

上下文应用场景

Python 的上下文管理器是一个强大的特性,它允许你以非常优雅和简洁的方式管理资源,比如文件操作、数据库连接、网络套接字等。使用上下文管理器可以确保即使在发生异常的情况下,资源也能被正确地清理和释放。上下文管理器通常与 with 语句一起使用。

以下是上下文管理器的一些典型应用场景:

1. 文件操作

文件操作是上下文管理器最常见的应用场景之一。使用 with 语句可以自动管理文件的打开和关闭,即使在读写文件时发生异常,文件也会被正确关闭。

1
2
3
with open('example.txt', 'r') as file:
content = file.read()
# 文件在这里已经被自动关闭

2. 线程锁(Threading Locks)

在多线程编程中,为了防止数据竞争,可能需要使用锁来同步对共享资源的访问。Python 的 threading 模块提供了锁(Lock 和 RLock),这些锁可以作为上下文管理器使用,以确保锁在使用后能被正确释放。

1
2
3
4
5
6
7
from threading import Lock

lock = Lock()
with lock:
# 执行临界区代码
pass
# 锁在这里被自动释放

3. 数据库连接

在处理数据库时,正确管理数据库连接非常重要。虽然标准的数据库连接库(如 SQLite, PostgreSQL 的 psycopg2 等)不直接提供上下文管理器接口,但你可以通过定义自己的上下文管理器类或使用第三方库(如 SQLAlchemy)来管理数据库连接。

1
2
3
4
5
# 假设 db_connect 是获取数据库连接的函数
with db_connect() as conn:
# 执行数据库操作
pass
# 连接在这里被自动关闭或回滚

4. 网络套接字

网络编程中,套接字(Sockets)的创建、使用和关闭也可以利用上下文管理器来管理,以确保即使在发生异常时,套接字也能被正确关闭。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import socket

class SocketContext:
def __init__(self, host, port):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.connect((host, port))

def __enter__(self):
return self.sock

def __exit__(self, exc_type, exc_val, exc_tb):
self.sock.close()

with SocketContext('localhost', 12345) as sock:
# 发送和接收数据
pass
# 套接字在这里被自动关闭

5. 临时目录和文件

在处理需要临时文件或目录的场景时,可以使用像 tempfile 这样的库来创建它们,并通过自定义上下文管理器来确保在不再需要时它们被正确删除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import tempfile
import shutil

class TempDir:
def __init__(self):
self.temp_dir = tempfile.mkdtemp()

def __enter__(self):
return self.temp_dir

def __exit__(self, exc_type, exc_val, exc_tb):
shutil.rmtree(self.temp_dir)

with TempDir() as tempdir:
# 使用临时目录
pass
# 临时目录在这里被自动删除

总结

上下文管理器应用广泛涉及到前后状态改变的都可以使用,也就是,任何需要确保资源在使用后被正确释放或清理的场景,都可以利用上下文管理器来实现。所以你掌握了其中的原理了吗?


Python上下文管理器
https://dreamshao.github.io/2024/08/15/python上下文管理器/
作者
Yun Shao
发布于
2024年8月15日
许可协议