上下文管理器
在我们日常程序处理过程中,总会遇到这样的情况,需要打开一个文件、与数据库建立一个连接,甚至在多线程中获取一个互斥锁等。这类情况具有相似的过程:
获取一个对象(文件对象、数据库对象、锁对象等);
在这个对象基础上做一些操作(文件读写、数据库读写、锁内容的修改等);
释放该对象(关闭文件、关闭数据库连接、释放锁等);
此外,这其中还可能存在潜在的异常需要处理(例如,文件可能不存在,数据库连接断开等)。整个过程类似开门进屋和关门离开的过程,中间的操作都在屋内这个环境下进行,而门锁则是进入和离开这个环境的核心。我们将屋内的环境称作上下文,简单理解就是一个处于特定状态的一段代码(文件打开状态),而这段代码需要一个进屋(打开文件)和锁门(关闭文件)的过程。我们以一个文件的读写为例来说明此事。一个比较鲁棒的文件读取示例如下:
filename='a.txt'
try:
f=open(filename,'r')
exceptFileNotFoundError:
print(
'File {} not exists'.format(filename)
)
importsys
sys.exit(-1)
else:
content=f.readlines()
print(content)
finally:
f.close()
保证当文件不存在时,程序会正常推出而不会崩溃;表示当文件正确打开后做的一系列操作;保证了在上述过程中,不论出现了任何问题都会关闭文件,释放资源。每次文件操作都需要上面一套流程来保证程序的健壮性,看起来很是繁琐。因此,Python给出了一个更加简洁的解决方案——上下文管理器和关键字。
来看看利用上下文管理器应当怎么改写上述代码:
filename='a.txt'
try:
withopen(filename,'r')asf:
content=f.readlines()
print(content)
exceptFileNotFoundError:
print(
'File {} not exists'.format(filename)
)
importsys
sys.exit(-1)
最大的区别在于:
文件描述符由获取;
没有了代码块,部分放进了代码段内;
这样当处理过程出现错误时,文件会被关闭吗?后面我们会知道,答案是会。上下文管理器在Python中同样是一类对象,它们的特点是具有和两个特殊方法。定义了进入这个上下文时要做的一些事,而则定义了离开这个上下文时要做的事。上下文管理器需要由语句调用,此时解释器会先执行方法,如果有返回值,可以利用来接收这个返回值。当离开这个上下文时(即缩进回到了同一级时),解释器自动执行方法。我们再针对上面打开文件的例子来详细描述一下整个过程。首先函数会打开一个文件,返回一个文件描述符对象。请注意,这个文件描述符对象才是我们的上下文管理器对象,而不是。那么这个文件描述符对象的时候,会自动执行它的方法。实际上,文件描述符对象的方法仅仅是把对象本身返回()。后面的接收了这个返回值(即文件描述符对象本身),并将其绑定到标识符上,这样,在上下文中间的代码就可以使用这个。当使用完毕后,离开上下文,自动执行方法。这个方法做的工作会复杂一些。首先它会调用文件描述符的方法来关闭它(这就是为什么我们不需要手动写一个语句来关闭它);其次,它还会处理过程中出现的异常,处理不了的异常还会重新向外层抛出(所以我们在外层包了一个语句)。我们来自定义一个上下文管理器来熟悉一下整套流程。正如前面说的,上下文管理器是个对象,它有和两个方法。需要注意一点的是,需要接收几个参数。我们先利用来忽略这些参数,另外它的返回值必须是布尔型的,来表示其中的异常是否需要再向外层抛出:
classContext:
def__enter__(self):
print('In enter')
def__exit__(self,*_):
print('In exit')
returnTrue
withContext():
print('In context')
print('Out of context')
# In enter
# In context
# In exit
# Out of context
根据打印结果我们可以看到上下文管理器的流程。我们来实现一个简易的对象:文件内容:欢迎关注微信公众号:它不只是Python
classOpenFile:
def__init__(self,name,mod):
self.f=open(name,mod)
def__enter__(self):
returnself.f
def__exit__(self,*_):
self.f.close()
print('File is closed automatically')
returnTrue
filename='a.txt'
withOpenFile(filename,'r')asf:
forlineinf:
print(line)
# 欢迎关注
#
# 微信公众号:
#
# 它不只是Python
# File is closed automatically
withopen(filename,'r')asf:
forlineinf:
print(line)
# 欢迎关注
#
# 微信公众号:
#
# 它不只是Python
是不是完全一致?下面我们再尝试把处理文件不存在的异常也放进管理器中来进一步简化:
classOpenFile:
def__init__(self,name,mod):
self.f=None
self.err=None
try:
self.f=open(name,mod)
exceptFileNotFoundErrorase:
print('File not exits')
self.err=e
def__enter__(self):
return(self.f,self.err)
def__exit__(self,*_):
ifself.f:
self.f.close()
returnTrue
filename='ab.txt'
withOpenFile(filename,'r')as(f,err):
ifnoterr:
forlineinf:
print(line)
# File not exits
这里如果文件不存在,我们将异常也通过返回出来,便可以利用一个语句来替代。关于上下文管理器的异常处理问题和标准库支持将放在系列的下期讲解。
欢迎关注 它不只是Python
领取专属 10元无门槛券
私享最新 技术干货