有时一些任务,需要事先做一些设置,事后做一些清理,这时 with 就可以出场了,with能够对这样的需求进行一个比较优雅的处理,最常用的例子就是对访问文件的处理。
文件读写初级:
一般访问文件资源时我们会这样处理:
f = open(r'c:\mytest.txt', 'r')
data = f.read()
f.close()
存在两个问题:
1. 如果在读写时出现异常而忘了异常处理。
2. 忘了关闭文件句柄
文件读写中级:
以下的加强版本的写法:
f = open(r'c:\mytest.txt', 'r')
try:
data = f.read()
finally:
f.close()
以上的写法就可以避免因读取文件时异常的发生而没有关闭问题的处理了。代码长了一些。
文件读写高级:
使用with有更优雅的写法:
with open(r'c:\test.txt', 'r') as f:
data = f.read()
说明:
with后面接的对象返回的结果赋值给f。此例当中open函数返回的文件对象赋值给了f;with会自已获取上下文件的异常信息。
with语句的工作原理
__enter__()/__exit__()这两个方法
with后面返回的对象要求必须有这两个方法,而文件对象f刚好是有这两个方法的。
object.__enter__(self)
进入与此对象相关的运行时,with语句将将此方法的返回值绑定到语句的AS子句中指定的目标(如果有设置的话)
object.__exit__(self, exc_type, exc_value, traceback)
退出与此对象相关的运行时,如果上下文运行时没有异常发生,那么三个参数都将置为None。
如果有异常发生,并且该方法希望抑制异常(即阻止它被传播),则它应该返回True。否则,异常将在退出该方法时正常处理。
注意:
__exit__()方法不应该重新抛出传入的异常,这是调用者的职责。
下面,以3个实例讲解:
1、无异常情况:
class Test:
def __enter__(self):
print('__enter__() is call!')
return self
def dosomething(self):
print('dosomethong!')
def __exit__(self, exc_type, exc_value, traceback):
print('__exit__() is call!')
print(f'type:{exc_type}')
print(f'value:{exc_value}')
print(f'trace:{traceback}')
print('__exit()__ is call!')
with Test() as sample:
sample.dosomething()
#运行结果:
>>>__enter__() is call!
>>>dosomethong!
>>>__exit__() is call!
>>>type:None
>>>value:None
>>>trace:None
>>>__exit()__ is call!
以上的实例Text,我们注意到他带有__enter__()/__exit__()这两个方法,当对象被实例化时,就会主动调用__enter__()方法,任务执行完成后就会调用__exit__()方法,另外,注意到,__exit__()方法是带有三个参数的(exc_type, exc_value, traceback), 依据上面的官方说明:如果上下文运行时没有异常发生,那么三个参数都将置为None, 这里三个参数由于没有发生异常,的确是置为了None, 与预期一致。
2、出现并抛出异常:
class Test:
def __enter__(self):
print('__enter__() is call!')
return self
def dosomething(self):
x = 1/0
print('dosomethong!')
def __exit__(self, exc_type, exc_value, traceback):
print('__exit__() is call!')
print(f'type:{exc_type}')
print(f'value:{exc_value}')
print(f'trace:{traceback}')
print('__exit()__ is call!')
# return True
with Test() as sample:
sample.dosomething()
#运行结果:
__enter__() is call!
__exit__() is call!
type:<class 'ZeroDivisionError'>
value:division by zero
trace:<traceback object at 0x0000026590B51F48>
__exit()__ is call!
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "<stdin>", line 6, in dosomething
ZeroDivisionError: division by zero
从结果可以看出, 在执行到dosomethong时就发生了异常,然后将异常传给了__exit__(), 依据上面的官方说明:如果有异常发生,并且该方法希望抑制异常(即阻止它被传播),则它应该返回True。否则,异常将在退出该方法时正常处理。当前__exit__并没有写明返回True,故会抛出异常,也是合理的,但是正常来讲,程序应该是不希望它抛出异常的,这也是调用者的职责,我们将再次修改__exit__, 将其返回设置为True
3、出现异常,阻止异常抛出:
class Test:
def __enter__(self):
print('__enter__() is call!')
return self
def dosomething(self):
x = 1/0
print('dosomethong!')
def __exit__(self, exc_type, exc_value, traceback):
print('__exit__() is call!')
print(f'type:{exc_type}')
print(f'value:{exc_value}')
print(f'trace:{traceback}')
print('__exit()__ is call!')
return True
with Test() as sample:
sample.dosomething()
#运行结果:
__enter__() is call!
__exit__() is call!
type:<class 'ZeroDivisionError'>
value:division by zero
trace:<traceback object at 0x0000026590B56208>
__exit()__ is call!
从结果看,异常抛出被抑制了,符合预期。