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有助于避免资源泄露地问题,让代码更加易于阅读。