前言
上一篇文章 python学习之【文件读写】 中我们学习了python当中的文件读写,这篇文章接着学习python中文件读写的with语句。
了解with语句
在很多场景中,通过使用with语句可以让我们可以更好地来管理资源和简化代码,它可以看做是对
try...finally模式的简化。
with语句的语法格式
with open(文件名,打开方式,编码格式) as 别名:
with语句体
with语句可以自动管理上下文资源,不论是什么原因跳出with块都能确保文件可以正确的关闭 ,以此来达到释放资源的目的。
举几个例子:
如果是利用以前的知识来进行文件读取的操作,流程就是打开文件------》读取文件------》关闭文件。
例如:
python
# 比如我们要读取一个文件,如果不用with语句实现的话:
#打开文件
file_a=open('a.txt','r')
#读取文件
print(file_a.read())
#关闭文件,释放资源
file_a.close()
但是假如在读取文件的过程中出现异常的话,会对我们后续的关闭文件操作产生影响,从而导致资源无法释放。那应该怎样实现在读取文件时无论有没有产生异常都让打开了的文件及时关闭呢?我们可能会想到用try...except去完成
python
try:
# 打开文件
reader=open('a.txt','r')
# 操作文件
content=reader.read()
print(content)
except Exception: #有异常打印出异常,避免程序性报错
print(Exception)
finally:
#关闭文件
reader.close()
try...except 的确会当对文件操作时发生异常不让程序发生报错,但是try...except是一种固定的格式,如果我们需要读取的文件有十几二十个呢,是不是需要按照try...except 的语法格式写很多条关闭操作的固定代码呀,这时候就出现了with语句来简化我们繁琐的代码:
python
#用with语句写 执行完毕with语句体之后会自动调用open中的__exit__方法对文件关闭,
#我们就不需要再写close()操作了
with open('a.txt','r') as file:
print(file.read())
我们看只需要两行代码就能实现文件的读写;下面这个例子会更直观的看出with语句的妙处:
python
# 不使用with语句复制图片
file_src=open('befor.png','rb')
file_tgt=open('after.png','wb')
file_tgt.write(file_src.read())
file_src.close()
file_tgt.close()
# 使用with语句复制图片
with open('befor.png','rb') as src_file:
with open('copy.png','wb') as tgt_file:
tgt_file.write(src_file.read())
我们能够看出使用with语句不仅简化了我们的代码书写,更避免了我们在读取文件完毕后忘记写入关闭文件的操作,增强了文件的安全性。
实现原理
上文我们提到了上下文管理器,其实with语句就是通过上下文管理器来实现对文件的打开关闭操作。
当一个类中包含__enter__ 方法 和 __exit__方法时,我们就可以称这个类为上下文管理器的类。
使用with执行这个类时,会自动调用类的__enter__方法;然后执行with 语句体的内容;
当with语句体的内容执行完毕后,最后再调用执行类中的__exit__方法 实现关闭文件的操作。
我们先用代码实现with语句的工作原理,以便让我们更熟练的使用with语句:
python
#定义一个类叫做MyContent
class MyContent():
# 特殊方法
def __enter__(self):
print('enter方法被调用了')
return self
# 特殊方法
def __exit__(self,exc_type,exc_val,exc_tb):
print('exit方法被调用了')
# 实例方法
def show(self):
print('实例方法show()被调用了')
with MyContent() as file:
file.show()
#离开运行时上下文(with语句体执行完毕),自动调用上下文管理器的特殊方法__exit__()
运行结果如下:
这个例子中,MyContent类就是一个上下文管理器的类,这个类中包含__enter__ 方法 和 exit__方法,当执行到with语句时,会自动调用该类中的__enter__方法;然后就开始执行with语句体,with语句体中我们通过类的对象调用了show()方法,因此show()方法被执行;最后当with语句体全部执行完毕后,就开始调用类中的__exit__方法让文件得以关闭。
由此,前文用with语句实现对文件读取的操作的工作原理也就显而易见了:
python
with open('a.txt','r') as file:
print(file.read())
with语句后紧跟的open其实就是一个含有__enter__ 方法 和 exit__方法的类,因此这个类就叫做上下文管理器的类。
我们可以调用python中定义好的open类,也可以自定义一个类,让它也具有open类的功能:
自定义一个类实现with语句
python
class File_Manager(object):
#定义一个初始化方法
def __init__(self,name,mode):
print('调用了__init__方法')
# 实例化对象
self.name=name
self.mode=mode
self.file=None
# 定义两个特殊方法 __enter__和__exit__方法
def __enter__(self):
print('调用了__enter__方法')
# 将打开的文件名和打开文件的方式赋给self.file
self.file=open(self.name,self.mode)
return self.file
def __exit__(self,exc_type,exc_val,exc_tb):
print('调用了exit方法')
# 如果指定文件被打开,就执行close()操作
if self.file:
self.file.close()
# 定义一个实例方法
def show(self):
print('show()被调用',self)
#使用with语句,这里的上下文管理器对象是我们创建的 File_Manager 我们在创建该类时对变量name和mode进行实例化了,因此这里需要给该类传递两个实例化对象
#文件名和 打开方式
with File_Manager('a.txt','r') as read_file:
content=read_file.read()
print('读取a.txt',content)
#调用实例方法show()
read_file=File_Manager('a.txt','r')
read_file.show()
运行结果:
我们定义了一个File_Manager()类,在这个类中定义了两个特殊方法__enter__() 方法和__exit__()方法,这两个方法分别负责对文件的开始和对文件的关闭;在执行with语句时,我们用自定义的File_Manager()类来替换原本的with语句中调用的open类,当执行with语句时,File_Manager()类就被调用,从而开始执行类中的__enter__() 方法和__exit__()方法以达到文件开启关闭的作用。
with语句读取文件运行流程:
1 当我们在进行实例化对象的时候,__init__初始化方法会自动被调用 ------》调用了__init__方法
2 当执行with语句的时候,会自动调用类中的__enter__方法 ------》调用了__enter__方法
3 当执行到__enter__方法的return sele.file 时,会返回一个文件对象,此时开始读取文件------》读取a.txt ------》hello world
4 当文件读取完毕时最后执行__exit__方法 ------》调用了exit方法
我们可以看到,当我们在with语句体中使用类的对象来调用类中的实例方法时,依然是先执行调用的
show()方法,然后再执行__exit__方法;
也就是说只有当with语句体的内容全部执行完毕后,才会执行__exit__方法关闭文件,释放资源
有关异常处理
上文我们说到with语句可以看作是对try...expect模式的简化,那么如果我们使用with语句来对文件进行操作时,with语句体中发生异常,会不会导致程序报错影响文件的关闭操作呢?
python
class MyContent():
# 特殊方法
def __enter__(self):
print('enter方法被调用了')
return self
# 特殊方法
def __exit__(self,exc_type,exc_val,exc_tb):
print('exit方法被调用了')
# 实例方法
def show(self):
print('实例方法show()被调用了')
# return 1/0 # 即使是实例方法中出现了异常,特殊方法__exit__都会被调用,使得文件关闭
with MyContent() as file:
a=1/0
file.show() #离开运行时上下文,自动调用上下文管理器的特殊方法__exit__()
我们在with语句体中写入了报错代码:a=1/0,这就相当于with语句体发生异常了,那么运行结果时怎样的呢:
从运行结果可以看出,即使在with语句体中出现异常,但是不会影响对__exit__()方法的调用,依然会对文件实行关闭操作。
但是虽然在发生异常时并没有影响文件的关闭,但是无论怎样程序还是报错了,那么with语句可不可以像try...expect模板那样将错误打印出来,避免程序的报错呢?我们不妨尝试一下:
我们看到python中根本就没有with...expect和with...else的语法,确实写不下去。
那我们是不是可以将try...expect和with联用呢,也就是将with语句作为一个块,然后嵌套在try...expect模块的try中:
python
# 尝试将with语句嵌套在try...expect
try:
with open('a.txt','r') as read_file:
a=1/0
print(read_file.read())
except Exception: #有异常打印出异常
print(Exception)
finally:
#关闭文件
read_file.close()
ok问题解决!
优化代码,将错误原因打印出来:
python
try:
with open("a.txt") as f:
a=1/0
print(f.readlines())
except Exception as error:
print(error)
finally:
f.close()
python
#用错误的方法读取二进制文件
try:
with open('pic1.png') as p:
print(p.read())
except Exception as error:
print(error)
finally:
print('文件已关闭')
p.close()
由此可知:with语句只能确保对文件操作时是否发生异常都可以及时关闭打开的文件;如果出现异常,程序依然会发生报错。
为什么会出现这种情况?
我们注意到在定义__exit__()这个特殊方法时,传入了三个参数:
__exit__(exc_type, exc_val, exc_tb):
这三个参数分别代表:异常类型 (exc_type)、值 (exc_value) 及回溯信息 (traceback)
退出与上下文管理器相关的运行时上下文,返回一个bool 值表示是否对发生的异常进行处理。
参数为引起退出操作的异常信息,如果退出时未发生异常,则3个参数均为None。
若发生异常,返回 True 表示无需处理异常,返回 False 则会在退出该方法后重新抛出异常以由 with 语句之外的代码逻辑进行处理。
若该方法内部产生异常,则会取代由 statement-body 中语句产生的异常。要处理异常时,不应显示重新抛出异常
(即不能重新抛出通过参数传递进来的异常),只需将返回值设为 False 即可。之后,上下文管理代码会检测是否 exit ()
失败来处理异常。
简单来说:
一个上下文管理器的__exit__方法,如果它返回False将在错误结束时重报错误。如果它返回True,它将抑制它。但是open内置的__exit__不返回True,因此当with语句体中执行出异常时,执行了__exit__方法关闭文件,错误还是会被重报,并不会避免。
由此可得当with语句体出现异常时,with语句采取的措施是:
1:将发生异常的类型 (exc_type)、值 (exc_value) 及回溯信息 (traceback) 作为实参传递给 exit
方法;
2:使得 exit 方法处理异常;
3:如果__exit__ 方法返回 True,则说明该异常已被"处理",无需再处理;
4:如果__exit__ 方法返回 True 之外的任何内容,则该异常将被 with 语句抛出;
说到这里,我们是不是可以通过对__exit__()功能的完善来处理异常发生呢?
python
class File(object):
def __init__(self, name, mode):
self.file = open(name, mode)
def __enter__(self):
return self.file
def __exit__(self, type, value, traceback):
print("异常已捕获")
self.file.close() # 关闭文件对象
print("文件已关闭")
return True # 返回 True 说明异常已被处理
with File('a.txt', 'r') as file:
#触发异常
a=1/0
file.read()
每篇一语
一辈子,别错把放纵当潇洒,别把颓废当自由。
如有不足,感谢指正!