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

(一)with语句

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

    with  表达式  [as 目标]:

            代码块

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

with expr1 as e1:
    with expr2 as e2:

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

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

使用with语句代码如下:

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

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

(1)计算表达式的值,返回一个上下文管理器对象

   (2)加载上下文管理器对象的__exit__()方法以备后用

(3)调用上下文管理器对象的__enter__()方法

(4)如果with语句中设置了目标对象,则将__enter__()方法的返回值赋值给目标对象(比如上面的f)

(5)执行with里的代码块

(6)如果步骤(5)代码正常结束,调用上下文管理器对象的__exit__()方法,返回值直接忽略

(7)如果步骤(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__()属性如下

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


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

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模块,这个下次有机会再讲。技术有限,不正之处,请多多包涵!