相信大家对Python中的with语句一定不陌生,尤其是在文件的读写操作中,但是我想大多数人可能已经习惯了它的用法,只是他们并不知道背后隐藏着“秘密”。那么,with语句如何使用,与之相关的上下文管理器(contextmanager)是什么,它们之间又是什么联系呢?本文将为您带来解密~什么是上下文处理器?在任何编程语言中,文件输入输出、数据库连接断开等都是很常见的资源管理操作。但是资源是有限的。我们在编写程序的时候,一定要保证这些资源在使用之后就被释放,否则很容易造成资源泄露,从而减慢系统处理速度,导致系统崩溃。只是说这些概念,你可能没有意识到这一点,我们可以看下面的例子:forxinrange(10000000):f=open('test.txt','w')f.write('hello')这里我们一共打开了10000000个文件,但是使用完后没有关闭,如果运行这段代码会报错:OSError:[Errno23]Toomanyopenfilesinsystem:'test.txt'这是资源泄漏的典型例子。因为程序中同时打开的文件太多,占用太多资源,导致系统崩溃。为了解决这个问题,不同的编程语言引入了不同的机制。在Python中,对应的解决方案是上下文管理器。上下文管理器可以帮助你自动分配和释放资源,最典型的应用就是with语句。因此,上面代码的正确写法应该是:forxinrange(10000000):withopen('test.txt','w')asf:f.write('hello')这样,每次我们打开文件“test.txt”写入'hello'后,该文件会自动关闭并释放对应的资源,防止资源泄露。当然,with语句的代码也可以用下面的形式表示:f=open('test.txt','w')try:f.write('hello')finally:f.close()它需要注意的是,最后的finally块尤为重要,即使写入文件时出现error异常,也能保证文件finally关闭。但是,与with语句相比,这样的代码是多余的,容易遗漏,所以我们一般更喜欢使用with语句。另一个典型的例子是Python中的threading.lock类。比如我要获取锁,执行相应的操作,完成后释放,代码可以这样写:some_lock=threading.Lock()some_lock.acquire()try:...finally:some_lock.release()对应的with语句也非常简洁:some_lock=threading.Lock()withsomelock:...从这两个例子我们可以看出,使用with语句可以简化代码,有效避免A资源发生泄漏。上下文管理器的实现Class-basedcontextmanager在了解了上下文管理的概念和优势之后,我们来看看上下文管理器的原理,并通过具体的例子来弄清楚它的内部实现。这里,我自定义了一个上下文管理类FileManager来模拟Python的打开和关闭文件操作:self.file=nonedef__enter__(self):print('调用__enter__方法')seld.file=Open(seld.name,seld.mode)Returnself__exit_(self,exc_type.:print('调用__exit__方法')ifself.file:self.file.close()withFileManager('test.txt','w')asf:print('readytowritetofile')writef.('helloworld')##Outputcalling__init__methodcalling__enter__methodreadytowritetofilecalling__exit__method需要注意的是,当我们使用一个类来创建上下文管理器时,必须保证这个类包含方法“__enter__()”和方法“__exit__()”其中,方法“__enter__()”返回需要管理的资源,方法“__exit__()”通常会有一些释放和清理资源的操作,比如作为本例中的关闭文件。而当我们使用with语句来执行这个上下文管理器时:withFileManager('test.txt','w')asf:f.write('helloworld')以下四个步骤将依次发生:1.方法“__init__()”被调用,程序初始化对象FileManager,使文件名(name)为“test.txt”,文件模式(mode)为'w';2、调用方法“__enter__()”,以写入方式打开文件“test.txt”,返回的FileManager对象赋值给变量f;3、将字符串“helloworld”写入文件“test.txt”;4.方法“__exit__()”被调用,负责关闭一个之前打开的文件流。因此,这个程序的输出是:calling__init__methodcalling__enter__methodreadytowritetofilecalling__exit__meth另外值得一提的是,方法“__exit__()”中的参数“exc_type、exc_val、exc_tb”分别代表exception_type、exception_value和追溯。当我们用上下文管理器执行with语句时,如果抛出异常,异常信息会被包含在这三个变量中,传递给方法“__exit__()”。因此,如果需要处理可能出现的异常,可以在“__exit__()”中添加相应的代码,例如这样写:classFoo:def__init__(self):print('__init__called')def__enter__(self):print('__Enter__呼叫')返回selfdef__exit__(self,exc_type,exc_value,exc_tb):print('__exit__nate')如果exc_type:print(f'exc_type:f'exc_type:{exc_type}'exc_value}')print(f'exc_traceback:{exc_tb}')print('异常处理')返回TruewithFoo()asobj:raiseException('exceptionraised').with_traceback(None)#输调用____enter__init__calledexc_type:
