概要 本文是上下文管理器专题第一篇,主要简单介绍上下文及上下文管理器的基本概念,分析with操作符的使用及其优缺点,最后还介绍了如何实现自定义上下文管理器
什么是上下文 程序中所谓的上下文件就是指程序所执行的环境状态,或者说程序运行的情景
关于return self
查看第四个代码块
上下文管理器定义 写代码时,我们希望把一些操作放到一个代码块中,这样在代码块中执行时就可以保持在某种运行状态,而当离开该代码块时就执行另一个操作,结束当前状态;所以,简单来说,上下文管理器的目的就是规定对象的使用范围,如果超出范围就采取“处理”,这一功能是在Python3.5之后引进的,它的优势在于可以使得你的代码更具可读性,且不容易出错。
常见的上下文管理器:with操作符 最常见的上下文管理器就是with语句了,python提供了with语句语法,来构建对资源的自动创建与自动释放,允许你在有需要的时候,精确地分配和释放资源
使用方法: 首先我们来看一段未使用with语句的代码,看看它在运行过程中会出现什么问题?
#when without using context manager
f = open("hl.txt", "w")
print(f.closed) #运行到这里,会打印出False,因为没有关闭
f.write("Hello,context manager!")
f.close()
print(f.closed) #这里才会打印出True,表示文件关闭
表面上看似这段代码也并没什么大的问题,但如果这段代码根本执行不到f.close()这一句就会出现问题,假设我们在写入的过程中,磁盘满了,代码就会在写入的那一句出现异常,这样就永远执行不到f.close()这一句,文件就永远不会被关闭,当然也可以用try…finally语句,如下所示:
#when using try…finally
try:
f = open("hl.txt", "w")
print(f.closed) #运行到这里,会打印出False,因为没有关闭
f.write("Hello,context manager!")
finally:
f.close()
print(f.closed) #这里才会打印出True,表示文件关闭
但是同样存在问题,当我们执行的不是简单的写入操作,而是其他更复杂的操作,如拷贝,同时读和写,try…finally语句根本无法保证代码的美感。这时,with就派上用场了。
下面,让我们来看一下使用了with的情况
#when using context manager
with open("hl.txt","w") as f:
print(f.closed) #False
f.write("Hello,context manager!")
print(f.closed) #True 无需执行f.close()
上面代码中,可以看到一个代码块:with…as… 其中,with关键词总是伴随着上下文管理器出现,as关键词将open(“hl.txt”,”w”)赋给了新的对象f,使得在该代码块中可以对f执行任意操作,代码相当简洁。
使用with的优点: 避免了琐碎操作:通过使用with,许多样板代码可以被消掉 避免了遗忘步骤:因此不用关注嵌套代码如何退出,又能确保我们的文件会被关闭
实现自定义上下文管理器 实现了上下文协议的函数/对象即为上下文管理器,上下文管理协议则是由enter和exit构成 要实现上下文管理器可以有两种方式,一种为基于类的实现,一种为基于生成器的实现
基于类的实现: 基于类的实现需要实现两个方法:enter()和_exit_() enter():负责进入代码块的准备工作,进入前被调用; exit():负责离开代码块的善后工作,离开后被调用;
class File(object):
def __init__(self, file_name, method):
self.file_obj = open(file_name, method)
def __enter__(self):
return self.file_obj
def __exit__(self, type, value, traceback):
self.file_obj.close()
任何定义了_enter_()和_exit_()方法的对象都可以用于上下文管理器,通过定义enter和exit方法,我们可以在with语句里使用它
with File('demo.txt', 'w') as opened_file: opened_file.write('Hola!') 我们的exit函数接受三个参数。这些参数对于每个上下文管理器类中的exit方法都是必须的。我们来看看在底层都发生了什么。
-
with语句先调用File类的__enter__方法
-
__enter__方法打开文件并返回给with语句
-
打开的文件句柄被传递给opened_file参数
-
我们使用.write()来写文件
-
with语句调用__exit__方法关闭文件。
处理异常 如果发生异常,Python会将异常的type,value和traceback传递给exit方法 尝试访问文件对象的一个不支持的方法
with File('demo.txt', 'w') as opened_file:
opened_file.undefined_function('Hola!')
exit方法返回的是None(如果没有return语句那么方法会返回None)。因此,with语句抛出了那个异常。
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
AttributeError: 'file' object has no attribute 'undefined_function'
当异常发生时,with语句会采取下面4步。
- 它把异常的type,value和traceback传递给exit方法。
- 它让exit方法来处理异常
- 如果exit返回的是True,那么这个异常就被优雅地处理了。
- 如果exit返回的是True以外的任何东西,那么这个异常将被with语句抛出
在exit方法中处理异常:
class File(object):
def __init__(self, file_name, method):
self.file_obj = open(file_name, method)
def __enter__(self):
return self.file_obj
def __exit__(self, type, value, traceback):
print("Exception has been handled")
self.file_obj.close()
return True
with File('demo.txt', 'w') as opened_file:
opened_file.undefined_function()
exit方法返回了True,因此没有异常会被with语句抛出
基于生成器的实现: 我们还可以用装饰器(decorators)和生成器(generators)来实现上下文管理器 Python有个contextlib模块专门用于这个目的。我们可以使用一个生成器函数来实现一个上下文管理器,而不是使用一个类。
from contextlib import contextmanager
@contextmanager
def file_open(path):
try:
f_obj = open(path,"w")
yield f_obj
except OSError:
print("We had an error!")
finally:
print("Closing file")
f_obj.close()
if __name__ == "__main__":
with file_open("test/test.txt") as fobj:
fobj.write("Testing context managers")
在这里,我们从contextlib模块中引入contextmanager,然后装饰我们所定义的file_open函数。这就允许我们使用Python的with语句来调用file_open函数。在函数中,我们打开文件,然后通过yield,将其传递出去,最终主调函数可以使用它。
一旦with语句结束,控制就会返回给file_open函数,它继续执行yield语句后面的代码。这个最终会执行finally语句--关闭文件。如果我们在打开文件时遇到了OSError错误,它就会被捕获,最终finally语句依然会关闭文件句柄。