前言
断断续续玩Python也有一段时间了,都是玩的很业余,写一些小的工具程序,基本上都是停留在能用的层面上。后来呢,自己想吧,不能总是这样学习吧,就寻思着把Python好好的、系统的、全面的学习一下,所以就买了几本书,想着这五一大过节的在家里安安静静的学习学习,陶冶陶冶,深造深造。可谁曾想,不看不知道,一看吓一大跳,这刚翻开没几页,就好几个模糊的知识点,而这篇将要总结的with
就是其中的一个。哎,不看了,撸串去~~~
抛出问题
之前写一些Python小工具,读写文件都是这样搞的:
#!/usr/bin/env python
fileReader = open('students.txt', 'r')
for row in fileReader:
print(row.strip())
fileReader.close()
基本也实现了读取文件的功能。但是有的时候,上述代码在运行的时候会抛出异常,导致无法关闭文件句柄,这个时候,我就会加上异常处理程序,代码就改成了这样:
#!/usr/bin/env python
try:
fileReader = open('students.txt', 'r')
for row in fileReader:
print(row.strip())
except:
print('Read file failed')
finally:
fileReader.close()
也还好,解决了抛出一大堆异常的问题。但是上述代码怎么看都有点累赘啰嗦的感觉。那怎么办?
解决问题
上面的代码虽然可以完成相应的功能,但是不简洁、很啰嗦,读起来也很麻烦。为了解决这种问题,从Python 2.5开始引入了with
语句,一种与异常处理相关的功能。
with
语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,比如文件使用后自动关闭、线程中锁的自动获取和释放等。比如上面的代码,通过使用with
语句改造,就变成了下面这个样子:
#!/usr/bin/env python
with open('students.txt', 'r') as fileReader:
for row in fileReader:
print(row.strip())
可以看到,通过使用with
语句重构,代码立马明朗了不少。这里虽然说了with
怎么使用,但是并没有说到点上,没法让人真的明白with
的原理。
深挖原理
为了更好的掌握with
语句的用法,明白其内在的原理。这里就深挖一下with
语句的原理,让大家知其然,更知其所以然。
要搞清楚with
语句的原理,先要说一下下面这几个概念:
- 上下文管理协议(Context Management Protocol):包含方法
__enter__()
和__exit__()
,支持该协议的对象要实现这两个方法。 - 上下文管理器(Context Manager):支持上下文管理协议的对象,这种对象实现了
__enter__()
和__exit__()
方法。上下文管理器定义执行with
语句时要建立的运行时上下文,负责执行with
语句块上下文中的进入与退出操作。通常使用with
语句调用上下文管理器,也可以通过直接调用其方法来使用。
说完上面两个概念,我们再从with
语句的常用表达式入手,一段基本的with
表达式,其结构是这样的:
with EXPR as VAR:
BLOCK
其中EXPR可以是任意表达式;as VAR是可选的。其一般的执行过程是这样的:
- 执行EXPR,生成上下文管理器context_manager;
- 获取上下文管理器的
__exit()__
方法,并保存起来用于之后的调用; - 调用上下文管理器的
__enter__()
方法;如果使用了as
子句,则将__enter__()
方法的返回值赋值给as
子句中的VAR; - 执行BLOCK中的表达式;
- 不管是否执行过程中是否发生了异常,执行上下文管理器的
__exit__()
方法,__exit__()
方法负责执行“清理”工作,如释放资源等。如果执行过程中没有出现异常,或者语句体中执行了语句break/continue/return
,则以None
作为参数调用__exit__(None, None, None)
;如果执行过程中出现异常,则使用sys.exc_info
得到的异常信息为参数调用__exit__(exc_type, exc_value, exc_traceback)
; - 出现异常时,如果
__exit__(type, value, traceback)
返回False,则会重新抛出异常,让with
之外的语句逻辑来处理异常,这也是通用做法;如果返回True,则忽略异常,不再对异常进行处理。
自定义上下文管理器
讲完了with
语句的内在原理,接下来我们就可以按照这个原理,实现我们自己的上下文管理器。自定义的上下文管理器要实现上下文管理协议所需要的__enter__()
和__exit__()
两个方法:
#!/usr/bin/env python
class DBManager(object):
def __init__(self):
pass
def __enter__(self):
print('__enter__')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print('__exit__')
return True
def getInstance():
return DBManager()
with getInstance() as dbManagerIns:
print('with demo')
代码运行结果如下:
__enter__
with demo
__exit__
这样我们就可以很轻松的自定义上下文管理器来对软件系统中的资源进行管理,比如数据库连接、共享资源的访问控制等。
总结
这篇文章通过实际的问题入手,分析问题,到最终的解决问题,环环相扣,通过通熟易懂的语言把Python中的with
语句进行了比较全面的总结,希望对大家的Python学习之路有所帮助。
果冻想,认真玩技术的地方。
2019年5月3日,于内蒙古包头。