这篇文章是关于什么的?什么是Python中的上下文管理器如何使用上下文管理器如何创建自己的上下文管理器关于Python上下文库(contextlib)1.什么是上下文管理器?比如你在写Python代码的时候,经常会在一个语句块中放入一系列的操作:当某个条件为真时-执行这个语句块当某个条件为真时-循环执行这个语句块有时候我们需要A程序在语句块中运行时保持某种状态,离开语句块后结束这种状态。所以,其实上下文管理器的任务就是——执行前准备代码块,执行后清理代码块。上下文管理器是Python2.5中添加的一个特性,它可以让你的代码更易读,更不容易出错。接下来,让我们看看如何使用它。2.如何使用上下文管理器?看代码是最好的学习方式,看看我们平时是怎么打开一个文件写“HelloWorld”的?filename='my_file.txt'mode='w'#Modethatallowstowritetothefilewriter=open(filename,mode)writer.write('Hello')writer.write('World')writer.close()1-2行,我们指定文件名和打开方式(写入)。第3行打开文件,第4-5行写入“Helloworld”,第6行关闭文件。这还不够,为什么我们需要上下文管理器?但是我们忽略了一个小而重要的细节:如果我们没有机会到达第6行来关闭文件,会发生什么情况?例如,磁盘已满,因此当我们尝试写入文件时在第4行抛出异常,而第6行永远没有机会执行。当然我们可以使用try-finally语句块进行打包:writer=open(filename,mode)try:writer.write('Hello')writer.write('World')finally:writer.close()finally语句block无论try块中发生什么,try块中的代码都会被执行。因此,可以保证文件将被关闭。这样做有什么问题吗?当然不是,但是当我们做一些比写“Helloworld”更复杂的事情时,try-finally语句就变得丑陋了。比如我们要打开两个文件,一个读一个写,并在两个文件之间进行复制操作,那么with语句可以保证两者可以同时关闭。好的,让我们分解一下:首先,创建一个名为“writer”的文件变量。然后,对writer进行一些操作。***,关闭编写器。这不是更优雅吗?withopen(filename,mode)aswriter:writer.write('Hello')writer.write('World')让我们更深入地挖掘一下,“with”是一个新的关键字,并且总是伴随着上下文管理器。“open(filename,mode)”曾经出现在之前的代码中。“as”是另一个关键字,它指的是从“open”函数返回的内容并将其分配给一个新变量。“writer”是一个新的变量名。2-3行,缩进开始一个新的代码块。在这个代码块中,我们可以对编写器进行任意操作。通过这种方式,我们使用“开放式”上下文管理器,它使我们的代码保持优雅和安全。它很好地完成了try-finally工作。open函数既可以用作简单函数,也可以用作上下文管理器。这是因为open函数返回一个文件类型(filetype)变量,而这个文件类型实现了我们之前使用的write方法,但是它必须实现一些特殊的方法才能作为上下文管理器使用,我将在下篇介绍款。3.自定义上下文管理器让我们编写一个“开放式”上下文管理器。要实现上下文管理器,必须实现两种方法——一种用于准备进入语句块,另一种用于离开语句块之后的处理。同时,我们需要两个参数:文件名和打开方式。Python类包含两个特殊方法,名为:__enter__和__exit__(双下划线作为前缀和后缀)。当对象用作上下文管理器时:在进入代码块之前将调用__enter__方法。__exit__方法在离开代码块后调用(即使在代码块中遇到异常)。下面是上下文管理器的示例,它在进入和离开代码块时进行打印。classPypixContextManagerDemo:def__enter__(self):print'Enteringtheblock'def__exit__(self,*unused):print'Exitingtheblock'withPypixContextManagerDemo():print'Intheblock'#Output:#Enteringtheblock#Intheblock#Exitingtheblock注意事项:没有传递参数。这里没有使用“as”关键字。__exit__方法的参数设置我们后面再讨论。我们如何将参数传递给类?事实上,在任何类中,都可以使用__init__方法,这里我们将重写它来接收两个必要的参数(文件名、模式)。当我们进入语句块时,会用到open函数,如第一个例子。当我们离开语句块时,__enter__函数中打开的所有内容都将关闭。这是我们的代码:(self,*unused):self.openedFile.close()withPypixOpen(filename,mode)aswriter:writer.write("HelloWorldfromournewContextManager!")让我们看看发生了什么变化:第3-5行,其中两行通过__init__参数接收。第7-9行,打开文件并返回。第12行,离开语句块时关闭文件。第14-15行使用我们自己的上下文管理器模拟打开。#p#另外,还有一些需要强调的地方:如何处理异常我们完全忽略了语句块内部可能出现的问题。如果语句块内发生异常,将调用__exit__方法并重新引发异常。在处理文件写入操作时,大多数时候你肯定不想隐藏这些异常,所以这样是可以的。对于我们不想重新抛出的异常,我们可以简单地让__exit__方法返回True来忽略语句块中发生的所有异常(这在大多数情况下不是明智之举)。当发生异常时,我们可以了解更详细的信息。完整的__exit__函数签名应该是这样的:def__exit__(self,exc_type,exc_val,exc_tb)这样__exit__函数就可以得到异常的所有信息(异常类型,异常值和异常跟踪信息),这些信息将有助于异常处理操作。这里不详细讨论如何写异常处理,下面是一个例子,只负责抛出SyntaxErrors异常。classRaiseOnlyIfSyntaxError:def__enter__(self):通过def__exit__(self,exc_type,exc_val,exc_tb):returnSyntaxError!=exc_type4。谈谈上下文库(contextlib)contextlib是一个Python模块,它提供了一个更易于使用的上下文管理器。contextlib.closing假设我们有一个创建数据库函数,该函数将返回一个数据库对象并在使用后关闭相关资源(数据库连接会话等)。我们可以照常处理,也可以通过上下文管理器来处理:withcontextlib.closing(CreateDatabase())asdatabase:database.query()contextlib.closing方法会在语句块结束后调用数据库的close方法。contextlib.nested的另一个很酷的特性可以有效地帮助我们减少嵌套:假设我们有两个文件,一个用于读取,一个用于写入,需要复制。以下内容已弃用:withopen('toReadFile','r')asreader:withopen('toWriteFile','w')aswriter:writer.writer(reader.read())可以通过contextlib.nested简化:withcontextlib。nested(open('fileToRead.txt','r'),open('fileToWrite.txt','w'))as(reader,writer):writer.write(reader.read())inPython2.7这个书写方式被新语法所取代:withopen('fileToRead.txt','r')asreader,\open('fileToWrite.txt','w')aswriter:writer.write(reader.read())contextlib.contextmanager对于高级Python玩家来说,任何可以被yield关键字拆分成两部分的函数都可以通过装饰器装饰的上下文管理器来实现。yield之前的任何操作都可以看做代码块执行前的操作,yield之后的任何操作都可以放在exit函数中。这里我举一个线程锁的例子:锁机制保证了两段代码同时执行时不会互相干扰。例如,如果我们有两段代码同时并行写入一个文件,我们将得到一个混合了两个输入的错误文件。但是如果我们可以有一个任何想要写入文件的代码都必须首先获取的锁,那么事情就会变得更容易。如果您想了解更多有关并发编程的知识,请参阅相关文献。下面是一个线程安全写函数的例子:managertoImplementation,回忆之前对yield和contextlib的分析:@contextlib.contextmanagerdefloudLock():print'Locking'lock.acquire()yieldprint'Releasing'lock.release()withloudLock():print'Lockislocked:%s'%lock.locked()print'Doingsomethingthatneedslocking'#Output:#Locking#Lockislocked:True#Doingsomethingthatneedslocking#Releasing特别注意,这不是异常安全的写法。如果您想要异常安全,请使用带有yield的try语句。幸运的是穿线。lock已经是上下文管理器了,所以我们只需要简单的:@contextlib.contextmanagerdefloudLock():print'Locking'withlock:yieldprint'Releasing'因为threading.lock在异常发生的时候会通过__exit__函数返回False,这当调用yield时将被重新抛出。在这种情况下,锁将被释放,但除非我们覆盖try-finally,否则不会执行对“print'Releasing'”的调用。如果你想在上下文管理器中使用“as”关键字,那么使用yield返回你需要的值,该值将通过as关键字赋值给新的变量。原文链接:pypix.com翻译:伯乐在线-KevinXiongCub翻译链接:http://blog.jobbole.com/64175/
