正好前几天有小伙伴在「测试开发群」里问「Python上下文管理器」有哪些使用场景。我感觉多数人应该经常用,但是换了个问法后就有点陌生了,所以我花点时间给大家整理下Python上下文管理器的知识点。
读写文件常规写法
在Python中,读写文件我们经常用下面这种写法:
with open("test_file.txt", "w+") as test_file:
print(test_file)
test_file.write("hello world")
这里其实就用到了with上下文管理器。它的工作原理是什么呢?
我们从代码字面意思可以猜到with open……语句返回的是一个操作文件的句柄对象(打印出来的值是:)。
然后我们直接使用这个TextIoWrapper进行文件的读写,但是还有个奇怪的问题,为什么用完test_file之后,我们不需要手动调用test_file.close()方法来关闭对象呢?
难道是系统会自动帮我们关闭IO对象吗?
追根溯源
好在Python是开源的,我们可以查看它的源码来验证我们的想法,在Pycharm中,直接点进去查看open方法的源码,但是却是个空实现,那真实的逻辑在哪里呢?
因为Python默认使用的是CPython,因此肯定有地方是能找到源码的,经过搜索,发现Python的源码就在github上放着。怎么找到对应的源码呢?
可以直接使用关键字搜索,我是用Pycharm中看到的方法注释「Open file and return a stream」,结果搜索出很多相关的代码段,github这个搜索功能貌似会将搜索语句分割成一个个关键字。没办法只能硬着头皮翻看头几页。
bingo!!找到如下图所示的内容:
其中一个是文件读写的C语言实现方式,另一个是py实现。我们就从py实现开始看,说不定就能找到我们需要的内容。还记得上面with open方法返回的是一个TextIOWrapper对象吧,我们直接在文件中搜索:「TextIOWrapper」,还真找到了,如下图所示:
接下来,我们需要找到它在哪里调用了close之类的方法来关闭文件读写的句柄。在TextIOWrapper类中,没找到有相关的操作,只能到它的父类TextIOBase中找找看。
在TextIOBase中只有常规的读和写的空实现,没找到我们需要的东西。只能再查看它的父类:IOBase,如果再找不到只能去C代码中碰碰运气。
功夫不负有心人啊,在IOBase中,我们看到了我们想找的代码,如下所示:
这个enter和exit方法又是怎么回事呢?网上查了下,它们和Python With上下文管理器有关系。所谓的上下文管理器是用来执行with语句时建立的运行时上下文的一个对象,通过调用对象的__ enter _「和_」 exit __ 方法来实现。
自定义一个With上下文管理器
了解完Python的With上下文管理器怎么使用,接下来我们就尝试自己动手写一个上下文管理器,加深大家对它的理解。我们直接定义一个有异常情况发生的代码:
class TestWith(object):
def __init__(self):
print("初始化")
def __enter__(self):
print("enter")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("exit")
print(exc_type)
print(exc_val)
print(exc_tb)
return True
def operation(self):
print('todo something')
print(1 / 0)
print('exception happened')
if __name__ == '__main__':
# # 使用自定义的上下文管理器
with TestWith() as f:
f.operation()
执行上面代码的结果如下所示:
初始化
enter
todo something
exit
<class 'ZeroDivisionError'>division by zero
<traceback object at 0x109bdcbe0>
奇怪了,为什么没有抛出异常,我们代码中明明使用了1/0。这就是上下文管理器高明的地方,它能对异常进行捕获处理。可以注意到__exit__方法有三个参数:exc_type, exc_val,exc_tb,它们分别表示:
- exc_type:异常类型
- exc_val:异常值
- exc_tb:异常的堆栈信息
在上面例子中,它返回的值是就是等,不过需要注意当我们程序主逻辑没有报错时,这三个参数将都返回None。
python上下文管理器的优势
我总结了我认为的几个优势:
- 它能让我们的代码变的简洁,比如:with open读写文件时,我们不需要再去调用close方法,避免遗忘造成的各种问题。
- 它能对异常进行捕获处理,其实也是让代码简洁了,我们不需要在代码中写很多try...except。
- 它能提高代码的复用率,比如:我们可以将一些常用的操作放到init(初始化操作)和exit(退出操作)方法中,这样方便其他人调用。
常驻小尾巴:欢迎加我的个人微信:xuanke_721,我拉大家加入测试开发群,和全国的测试小伙伴一起交流沟通!!