python上下文管理器和with语句

python中的with语句是一个晦涩的特性,但是有助于编写更加清晰易读的python代码。它有助于简化一些通用的资源管理模式,抽象出其中的功能,将其分解并重用。

Python标准库中的内置的open()函数就是一个很好的用例:

1 with open('hello.txt', 'w') as f:
2     f.write('hello world')

打开文件时一般建议使用with语句,因为这样能够确保打开的文件描述符在程序执行离开时with语句的上下文后自动关闭。本质上说,上面的代码可以替换成下面这样:

1 f = open('hello.txt', 'w')
2 try:
3     f.write('hello world')
4 finally:
5     f.close()

上面这段代码比with语句冗长。但是其中的try......finally语句很重要,只关注其中的逻辑代码还不够:

1 f = open('hello.txt', 'w')
2 f.write('hello world')
3 f.close()

如果在调用f.write()时发生异常,这段代码不能保证文件最后被关闭,因此程序可能会泄露文件描述符。因此with语句就可以派上用场,它能够简化资源的获取和释放。

with语句不仅让处理系统资源的代码更易读,而且由于绝对不会忘记清理或者释放资源,因此还可以避免bug或者资源泄露。

在自定义对象中支持with

只要实现上下文管理器(context manager),就可以在自定义的类和函数中获得同样的功能。

何为上下文管理器,这是一个简单的"协议"(或者接口),自定义对象需要遵循这个接口来支持with语句。总的来说,如果想将一个对象作为上下文管理器,需要做的就是向其中添加__enter__和__exit__方法。python将在资源管理周期的适当时间调用这两种方法。

下面是一个上下文管理器的简单实现:

1 class ManagedFile:
 2     def __int__(self, name):
 3         self.name = name
 4         
 5     def __enter__(self):
 6         self.file = open(self.name, 'w')
 7         return self.file
 8     
 9     def __exit__(self, exc_type, exc_val, exc_tb):
10         if self.file:
11             self.file.close()

其中的ManagedFile类遵循上下文管理器协议,所以与原来的open()例子一样,也支持with语句:

1 with ManagedFile('hello.txt') as f:
2     f.write('hello world')
3     f.write('bye now')

运行结果:

 

 

 当执行流程进入with语句上下文时,python会调用__enter__获取资源,离开with上下文时,python会调用__exit__释放资源。

在python中,除了编写基于类的上下文管理器来支持with语句外,标准库中的contextlib模块在上下文管理器基本协议的基础上提供了更多抽象。

使用contextlib.contextmanager装饰器能够为资源定义一个基于生成器的工厂函数,该函数将自动支持with语句。下面的示例使用这种技术重写了之前的ManagedFile上下文管理器:

 1 from contextlib import contextmanager
 2 
 3 
 4 @contextmanager
 5 def managed_file(name):
 6     try:
 7         f = open(name, 'w')
 8         yield f
 9     finally:
10         f.close()
11 
12 
13 with managed_file('hello.txt') as f:
14     f.write('hello world!')
15     f.write('\nbye now')

运行结果如下:

 

 

 这个managed_file()是生成器,开始先获取资源,之后暂停执行并产生资源以供调用者使用。当调用者离开with上下文时,生成器继续执行升序的清理步骤,并将资源释回系统。

基于类的实现和基于生成器的实现基本上是等价的。

用上下文管理器编写漂亮的API

上下文管理器非常灵活,巧妙地使用with语句能够为模块和类定义方便的API。

 1 class Indenter:
 2     def __init__(self):
 3         self.level = 0
 4 
 5     def __enter__(self):
 6         self.level += 1
 7         return self
 8 
 9     def __exit__(self, exc_type, exc_val, exc_tb):
10         self.level -= 1
11 
12     def print(self, text):
13         print('   ' * self.level + text)
14 
15 
16 with Indenter() as indent:
17     indent.print('hi!')
18     with indent:
19         indent.print('hello!')
20         with indent:
21             indent.print('bonjour')
22     indent.print('hey')

运行结果如下:

 

 总结

  • with语句通过在所谓的上下文管理器中封装try.....finally语句的标准用法来简化异常处理。
  • with语句一般用来管理系统资源的安全获取和释放。资源首先由with语句获取,并在执行离开with上下文时自动释放。
  • 有效地使用with有助于避免资源泄露地问题,让代码更加易于阅读。

posted @ 2020-11-06 11:27  蟹老板bb  阅读(68)  评论(0编辑  收藏  举报