巧用with语句让python程序更优秀概述学习Python有一段时间了,最近做一个项目会涉及到文件的读取和关闭。比如:我想把一些对象序列化到文件里面,然后当我再次使用的时候,在从文件里面读取反序列化成对象。像这种操作一般都是用try…except…finally。但是经过自己对Python的研究发现会有更出色的方法,比如:with-as语句也有的人称为contextmanager。With-As我们先看一下例子,当我们需要打开一个文件的时,比如:txt等,一般经常会这么操作:1234567try:f=file.open('test.txt','rw')ToDoexcept:ToDofinally:f.close()这是错误,因为file.open是否打开文件是不确定,而在出现异常的时候你却关闭了已经打开的文件。文件没有打开怎么能直接关闭呢?你可以按照下面的解决方法来解决上述出现的问题。1try:23456789101112131415f=file.open('test.txt','rw')ToDoexcept:ToDo//出现异常直接返回或者退出,这说明file并没有打开。return/exit(-1)//已经成功打开file文件,所以你需要在finally中关闭打开的文件。try:ToDoexcept:ToDofinally:f.close()你会发现这么做会非常麻烦,并且try……except…..finally嵌套也比较啰嗦。那有没有好的解决办法能解决上述问题,并且还能减少代码量呢?(类似于C#中的using关键字)答案是肯定的,那就是with…….as语句。With语句适用于对I/O、文件流、数据流等资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,比如文件使用后自动关闭、线程中锁的自动获取和释放等等。with语句的用法12withexpression[asvariable]:with-block表达式被计算(求值)依赖一个支持ContextManagementProtocol(必须包含:__enter__()和__exit__()的方法)的对象。因为expression返回一个ContextManager(上下文管理器)对象,如果指定了asvariable会将上下文管理器对象__enter__()方法的返回值赋值给variable;如果没有指定asvariable则丢弃。with-block在执行语句体之前会调用上下文管理器的__enter__()方法,执行完语句体之后会执行__exit__()方法,即使代码出现异常也会运行“清理”代码。例如如下代码:1234withopen('/etc/passwd','r')asf:forlineinf:printline...moreprocessingcode...这个语句执行完成之后,不管在处理文件过程中是否发生异常,都能保证with语句执行完毕后已经关闭了打开的文件句柄,确实比try……except……finally好多了。在这个例子中f就是上下文管理器__enter__()的返回值,返回的是当前文件自身的引用。Python内建对象都加入了对上下文管理器的支持,可以用在with语句中。比如:file、threading、decimal等等,在多线程模块中,lock和条件变量也是支持with语句的。例如:1234lock=threading.Lock()withlock:#Criticalsectionofcode...在代码执行之前lock总是先获得,只要block代码完成lock就会被释放。要想彻底了解Python的With-As语句,请继续往下看。Python术语ContextManagementProtocol(上下文管理协议):包含方法__enter__()和__exit__(),支持该协议的对象要实现这两个方法。上下文管理器(ContextManager):支持上下文管理协议的对象,这种对象实现了__enter__()和__exit__()方法。上下文管理器定义执行with语句时要建立的运行时上下文,负责执行with语句块上下文中的进入与退出操作。通常使用with语句调用上下文管理器,也可以通过直接调用其方法来使用。123456789101112131415161718192021context_manager=context_expressionexit=type(context_manager).__exit__value=type(context_manager).__enter__(context_manager)exc=True#True表示正常执行,即便有异常也忽略;False表示重新抛出异常,需要对异常进行处理try:try:target=value#如果使用了as子句with-body#执行with-bodyexcept:#执行过程中有异常发生exc=False#如果__exit__返回True,则异常被忽略;如果返回False,则重新抛出异常#由外层代码对异常进行处理ifnotexit(context_manager,*sys.exc_info()):raisefinally:#正常退出,或者通过statement-body中的break/continue/return语句退出#或者忽略异常退出ifexc:exit(context_manager,None,None,None)#缺省返回None,None在布尔上下文中看做是False上下文管理协议的高层解释:with表达式执行生成一个叫做上下文管理器的对象,上下文管理器必须包含__enter__()和__exit__()方法,并且要实现该两个方法。上下文管理器的__enter__()方法被调用,返回值将赋值给var,如果没有asvar,则返回值被丢弃。执行With-Body语句体。不管是否执行过程中是否发生了异常,执行上下文管理器的__exit__()方法,__exit__()方法负责执行“clean-up”工作,如释放资源等。如果执行过程中没有出现异常,或者语句体中执行了语句(break/continue/return),则以None作为参数调用__exit__(None,None,None);如果执行过程中出现异常,则使用sys.exc_info得到的异常信息为参数调用__exit__(exc_type,exc_value,exc_traceback),通常返回值是一个tuple,(type,value/message,traceback)。出现异常时,如果__exit__(type,value,traceback)返回False,则会重新抛出异常,让with之外的语句逻辑来处理异常,这也是通用做法;如果返回True,则忽略异常,不再对异常进行处理。运行时上下文(runtimecontext):通过上下文管理器创建,并由上下文管理器的__enter__()和__exit__()方法实现,__enter__()方法在语句体执行之前进入运行时上下文,__exit__()在语句体执行完后从运行时上下文退出。返回一个布尔值表示是否对发生的异常进行处理。如果退出时没有发生异常,则3个参数都为(None,None,None)。如果发生异常,返回True:不处理异常,否则会在退出该方法后重新抛出异常以由with语句之外的代码进行处理。如果该方法内部产生异常,不能重新抛出通过参数传递进来的异常,只需要returnFalse就可以。之后,上下文管理代码会检测是否__exit__()失败来捕获和处理异常。12345678910111213141516171819202122232425262728293031323334#!/usr/bin/envpython#-*-coding:utf-8-*-classCursor(object):defexecute(self,msg):printmsgclassDatabaseConnection(object):defcommit(self):printCommitscurrenttransactiondefrollback(self):printRollsbackcurrenttransactiondef__enter__(self):printGointo__enter__()cursor=Cursor()returncursordef__exit__(self,exc_type,exc_value,exc_tb):printGointo__exit__()#raiseException(__exit__......Exception)ifexc_tbisNone:#如果没有异常,则提交事务printExitedWithoutExceptionself.commit()else:#如果有异常,则回滚printExitedwithexceptionraisedprinttype:[,exc_type,],value:[,exc_value,],exc_tb:[,exc_tb,]self.rollback()if__name__==__main__:db_connection=DatabaseConnection()withdb_connectionascursor:cursor.execute(insertinto......)cursor.execute(deletefrom......)代码运行效果如下:123456Gointo__enter__()insertinto......deletefrom......Gointo__exit__()ExitedWithoutExceptionCommitscurrenttransaction上述代码正好验证了我们之前的分析,当运行withdb_connection运行时,进入我们自定义的__enter__()方法,当执行完with包裹的代码块时,就会进入__exit__()方法,如果没有异常(通过exc_tb是否为None来判断,当然也可以用其他两个参数判断。)则执行相应的代码逻辑。123456789101112131415161718#!/usr/bin/envpython#-*-coding:utf-8-*-classCursor(object):defexecute(self,msg):printmsgclassDatabaseConnection(object):defcommit(self):printCommitscurrenttransactiondefrollback(self):printRollsbackcurrenttransactiondef__enter__(self):printGointo__enter__()cursor=Cursor()returncursordef__exit__(self,exc_type,exc_value,exc_tb):printGointo__exit__()1920212223242526272829303132333435#raiseException(__exit__......Exception)ifexc_tbisNone:#如果没有异常,则提交事务printExitedWithoutExceptionself.commit()else:#如果有异常,则回滚printExitedWithExceptionraisedprinttype:[,exc_type,],value:[,exc_value,],exc_tb:[,exc_tb,]self.rollback()if__name__==__main__:db_connection=DatabaseConnection()withdb_connectionascursor:cursor.execute(insertinto...