Python中with语句的作用
with语句
python中的with语句主要是为了解决文件操作问题的
问题引入
如果我们想要写入一份叫做file1的文件,可能会写出下列代码:
1 | file = open("file1.txt", "r") |
file1已有内容
1 | something1 |
程序运行结果如下:
但当我们将文件删除后,再次执行程序出现了如下异常:
这个异常并没有得到处理,而且如果我们进行测试
1 | file = open("file1.txt", "r") |
结果和上面一样,说明close方法并没有被执行,file指针没有被正常释放,造成了(句柄)内存的泄漏。
尝试解决
使用try-except语句解决
我们将调用open方法的语句放入try语句块中,在except语句块中捕获异常
1 | try: |
程序运行结果
可以看到异常被捕获并处理了。
但每次都这么写太麻烦了,所以便引入with语句来简便地完成这项工作:
使用with语句解决
上述代码可以改写成
1 | with open("file1.txt", "r") as file: |
当然,在执行时,异常并没有被捕获。
这是怎么一回事呢?
深入探究with语句
其实,with语句的作用是调用其后面函数所在类的__enter__方法,将__enter__方法的返回值赋予as指向的变量,并在代码块执行完毕后调用__exit__方法,并在其中处理所捕获的异常。
我们可以写一段代码来测试:
1 | class Test_class: |
运行结果如下:
可以看到,在执行程序时,with语句先调用了其指向的Test_class函数(也是构造函数),然后进入了__enter__函数,将其返回值(“foo”)赋予test_class变量,最后在退出代码块时调用了__exit__函数。
如果在执行with语句后的函数时就出现异常会发生什么?
我们将程序进入位置的语句改成如下样式:
1 | with Test_class.do_something() as test_class: |
运行结果如下:
这个异常被抛出了。
这说明,异常仅在发生在被with语句引出的代码块中时才会被__exit__函数捕获,而如果异常发生在调用with语句后的函数的执行过程中时则会被抛出,而这也能解释为什么在open函数尝试以只读模式打开不存在的文件时发生的异常被抛出。
当with语句后接的是一般表达式时也是这样:
1 | with 1 / 0 as test_class: |
这说明with语句首先会执行其后的语句。
但当我们试图将简单表达式的值赋予as后的变量时程序执行却会出现异常:
1 | with 1 as test_class: |
报错信息显示,程序试图执行__enter__方法,但它并不存在。
耐人寻味的是,同样的语句在windows系统中执行,报错信息却不一样(文章默认使用Linux系统):
这个context manager protocol是什么东西呢?它其实是Python的一种被称为上下文管理器的规范,它要求对象内定义__enter__和__exit__方法,从而正确管理资源和处理异常。
因此,with后所执行的表达式并不是一般的表达式,它的值必须是一个定义了__enter__和__exit__方法的对象。with语句在执行完表达式内容后将__enter__方法的返回值赋予as后的变量,这才是它的真正执行逻辑。
而当异常发生在with引出的代码块中时,异常被正确捕获了:
1 | with Test_class() as test_class: |
这印证了我们刚才的说法。
总结
with(或with-as)语句的作用是执行with后的表达式,再在代码块的开端调用(表达式的值的)__enter__方法,将其返回值赋予as后的变量,再在代码块结束或异常被抛出时调用(表达式的值的)__exit__方法,在其中捕获并处理异常(如果有的话)。
参考文献
- 什么是异常?为什么要抛出异常?-CSDN博客
- Python_文件不关闭的后果——建议用with语句自动管理文件的关闭——六月十四-CSDN博客
- Python 中的 with 语句原来是这样的-知乎
- Python 中 with用法及原理_python中with用法-CSDN博客
- Python入门 class类的继承-知乎
- Python with 关键字 | 菜鸟教程
- python中的“表达式,语句,函数,方法”四者各指什么?其之间有何区别?-知乎
- 什么是Python中的上下文管理器(context manager)?如何使用上下文管理器?-腾讯云开发者社区-腾讯云
- attribute error是什么异常-掘金