在对一些资源进行访问时,常常会出现操作不当或出现异常而导致资源没有得到必要关闭资源释放资源。例如:文件读取、socket等等。
下面内容以文件读取open方法为例。
f=open("filename")
f.write()#文件操作
f.close()
上述代码存在的问题:
(1)容易忘记文件关闭。
(2)当文件操作出现异常导致程序提早离开,而没有执行关闭文件操作。
try:
f=open("xxx")
f.write() #文件操作
except:
do something
finally:
f.close()
上述代码:
虽然解决因为出现异常而导致没有关闭文件的问题。但是这样使得代码冗余度加大,最最重要的是这样一点都不pythonic。
with open("xxx") as f:
f.write() #文件操作
问题来了,
with为什么可以做到自动关闭文件?
with为什么即便文件操作中出现异常也可以正常关闭文件?
with语句和原始open同样返回一对象,有什么不一样?
基本思想是with所求值的对象必须有一个__enter__()方法,一个__exit__()方法。
紧跟with后面的语句被求值后,返回对象的__enter__()方法被调用,这个方法的返回值将被赋值给as后面的变量。当with后面的代码块全部被执行完之后,将调用前面返回对象的__exit__()方法。
我们具体来看看with为什么可以做到自动关闭资源。
1 class Test(object):
2 def __enter__(self):
3 print("执行了 __enter__方法")
4 return "enter返回的内容"
5
6 def __exit__(self, type, value, trace):
7 print("执行了 __exit__方法")
8
9
10 with Test() as test:
11 print("test:", test)
运行结果:
执行了 __enter__方法
test: enter返回的内容
执行了 __exit__方法
with后面的语句执行、enter()执行
enter()返回值返回给as后面的变量test
执行with语句中间代码块打印变量test、
中间代码块执行完后执行__exit__()
推断:自动关闭文件是在__exit__()中调用文件关闭方法。
接下我们改进一下代码来看看with为什么可以处理异常出现的情况
class Test(object):
def __enter__(self):
print("执行了 __enter__方法")
return self
def __exit__(self, type, value, trace):
print("执行了 __exit__方法")
print("type:", type)
print("value:", value)
print("trace:", trace)
def do_something(self):
bar = 1 / 0
return bar + 10
with Test() as test:
test.do_something()
运行结果:
执行了 __enter__方法
执行了 __exit__方法
Traceback (most recent call last):
File "E:/Code/PycharmProjects/untitled7/t2.py", line 19, in
type:
test.do_something()
value: division by zero
trace:
File "E:/Code/PycharmProjects/untitled7/t2.py", line 14, in do_something
bar = 1 / 0
ZeroDivisionError: division by zero
进程完成,退出码 1
先给分析一下代码,Test()的__enter__()方法返回新创建的Test对象,并赋值给变量test。然后执行会出现异常的方法,exit()中打印其三个参数。
根据运行结果,很明显__exit__()得三个参数分别是异常类、异常值、异常信息追踪。
实际上,当with中间代码体出现异常时__enter__()就会执行,并把异常相关信息赋值给三参数。同时在这个方法中我们还可以加入清理资源,关闭文件等等操作。
总的看来,python的with语句是一个十分巧妙有效的机制,它可以让代码更加的简洁、更加的pythonic。