Python with语句和上下文管理器对象

纠结过去,担心未来,都不如抓住当下。过去是梦,未来是影,现在才是真真切的人生。

我们平时编程时尽量使用with自动关闭资源,这里就有个上下文管理器对象的概念,然后查找资料,对with的解释如下:with的有一些任务,可能事先需要设置,事后做清理工作。对于这种场景,Python的with语句提供了一种非常方便的处理方式。一个很好的例子是文件处理,需要获取一个文件句柄,从文件中读取数据,然后关闭文件句柄。下面就讲下with语句以及上下文管理器对象,希望对你有帮助。

with语句

对于文件操作完成后,应该要关闭它,这是一个常识,因为打开的文件不仅占用了系统资源,而且可能影响其它程序或进程的操作,甚至会导致用户期望与实际操作结果不一样。with语句得语法为

1
2
with  表达式  [as 目标]:
代码块

with语句支持嵌套,支持多个with子句,它们两者可以相互转换。”with expr1 as e1,expr2 as e2”与下面的嵌套形式等价

1
2
with expr1 as e1:
with expr2 as e2:

with语句使用比较简单。如下面例子不使用with的时候代码如下:

1
2
3
f = open('test.txt','w')
f.write("hello")
f.close()#这句很容易被忘记,这也是为什么推荐使用with

使用with语句代码如下:

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

with语句可以在代码块执行完毕后,还原到进入该代码块时的现场(这句话要仔细理解,也就是说with里的代码块执行完后,会返回到刚刚进入with时的现场)。with语句代码块执行过程如下:

  • 计算表达式的值,返回一个上下文管理器对象
  • 加载上下文管理器对象的__exit__()方法以备后用;
  • 调用上下文管理器对象的__enter__()方法;
  • 如果with语句中设置了目标对象,则将__enter__()方法的返回值赋值给目标对象(比如上面的f);
  • 执行with里的代码块;
  • 如果步骤(5)代码正常结束,调用上下文管理器对象的__exit__()方法,返回值直接忽略;
  • 如果步骤(5)中代码异常,调用上下文管理器对象的__exit__(),并将异常类型、值以及traceback信息作为参数传递给__exit__()方法。如果__exit__()返回值为false,则异常会被重新抛出;如果返回的是true,异常被挂起,程序继续执行;

使用with的好处是无论程序以何种方式跳出with块,总能保证资源被正确关闭。下面介绍一下上下文管理器对象。

上下文管理器对象

with的神奇之处得益于一个成为上下文管理器的(context manager)的东西,它用来创建一个这样的对象:它定义程序运行时需要建立的上下文,处理程序的进入和退出,实现上下文管理协议,即在对象中定义__enter__()__exit__()方法(这两个方法可以重载,这就说明,我们可以自定义属于自己的上下文管理器,待会儿再介绍),其中:

  • __enter__(self)

    进入运行时的上下文,也就是进入上下文管理器时调用该函数,返回运行时的上下文对象,with语句中会将这个返回值绑定到目标对象上(上面的例子就是绑定到f上)。顺便说下上下文表达式(Context Expression),上下文表达式指with 语句中跟在关键字 with 之后的表达式,该表达式要返回一个上下文管理器对象,该对象就被赋值给了目标对象。

  • __exit__(self,exception_type,exception_value,traceback)

    退出运行时的上下文,定义在块执行(或终止)之后上下文管理器应该做什么。它可以处理异常、清理现场或者处理with块中语句执行完成后需要处理的动作。exception_type,exception_value,traceback三个参数代表的意思分别是异常的类型、值和追踪信息。如果没有异常,3个参数均设为None。此方法返回值为True或者False,分别指示被引发的异常得到了还是没有得到处理。如果返回False,引发的异常会被传递出上下文。这个在前面简单的提到过,希望你能结合上下文仔细理解这些东西。

实际上任何实现了上下文协议的对象都可以称为一个上下文管理器,文件也是实现了这个协议的上下文管理器,它们都能够与with语句兼容。文件对象的__enter__()__exit__()属性如下:

1
2
3
4
>>>f.__enter__
<built-in method __enter__ of file object at 0x029F0700>
>>>f.__exit__
<built-in method __exit__ of file object at 0x029F0700>

当然我们也可以定义自己的上下文管理器,只要实现了上下文协议便可以和with语句一起使用。如下面例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class OpenFile(object):
def __init__(self,filename,mode):
self.filename=filename
self.mode=mode
def __enter__(self):
self.f=open(self.filename,self.mode)
return self.f #作为as说明符指定的变量的值
def __exit__(self,exception_type,exception_value,traceback)
if exception_type is None:#如果没有异常,正常关闭资源
self.f.close()

else:#有异常发生
print exception_value
print traceback
return False#返回false则异常会被重新抛出

with OpenFile('my_file.txt','w') as f:
f.write('Hello')
f.write('World')

上下文管理器主要作用于资源共享,因此在实际应用中__enter__()__exit__()方法基本用于资源分配以及释放相关的工作,如打开/关闭文件、异常处理、断开流的连接、锁分配等。为了更好的辅助上下文管理器,Python还提供了contextlib模块,这个下次有机会再讲。

原文:with语句和上下文管理器对象

-------------本文结束 感谢您的阅读-------------
作者Magiceses
有问题请 留言 或者私信我的 微博
满分是10分的话,这篇文章你给几分