人是随着时间不断进化而来的,同样编程语言也是随着IT行业的更新换代,功能模块不断地优化与丰富才壮大起来的。比如在python2.5之前使用open读写文件操作就要注意。比如Python 程序打开一个文件后,往文件中写内容,写完之后,就要关闭该文件,否则会出现什么情况呢?极端情况下会出现 "Too many open files" 的错误,因为系统允许你打开的最大文件数量是有限的,默认打开文件最大文件数1024。
所以实际开发中要对可能发生异常的代码处进行 try 捕获,使用 try/finally 语句,因为如果在 try 代码块中程序出现了异常,后续代码就不再执行,而直接跳转到 except 代码块,但finally 块的代码无路如何最终都会被执行。因此,只要把 close 放在 finally 代码中,文件就一定会关闭,解决了这种隐藏的问题。
1. 使用open读写文件,try捕捉异常处理,finally关闭文件
def f1():
f = open("aaa.txt", "w+")
try:
f.write("hello ,world")
except IOError:
print("io 异常了啦")
finally:
f.close()
f1()
使用上面处理当然没有任何问题,但在python2.5以后,基于之前的try....except....finally增加了一个功能更加简洁的方式with关键字。with语句相对try/finally来说简洁了很多,而且也不需要每一个用户都去写f.close()来关闭文件了
2. 使用with关键字进行文件操作
def m2():
with open("aaaa.txt","w+") as f : #open 方法的返回值给变量f,所以这里f可以自定义名称
f.write("hahhahaha")
m2()
- open 方法的返回值赋值给变量 f,这里f只是变量名,指向open返回值的引用。
- 当离开 with 代码块的时候,系统会自动调用 f.close() 方法, with 的作用和使用 try/finally 语句是一样的,所以这里不用手动写close()了
上面那么为什么with可以实现这么强大的功能呢,即替代了try...finally,不用捕捉异常了,也不用手动调用close了,要想弄明白这个问题,先了解下python中的上下文管理器(Context Manager)。
3. 什么是上下文管理器
查看官网看是说:任何实现(重写)了 enter() 和 exit() 方法的对象都可称之为上下文管理器,搞不清context manager,内容管理器,为啥翻译成上下文管理器。使用pycharm可以查看原来enter() 和 exit() 是object类中自带的魔法方法。
细心的人用ctrl键打开查看该方法的源码,发现打开不了,这是为啥子呢?因为python的解释器底层是用c语言写的(也有用java的)。所以底层很多方法,都是查看不了源码的。要想查看源码可以到github上搜搜。但是一些导入的python库,用python实现的,是可以直接查看源码的。
所谓的上下文管理器其实是一个遵守了context management protocol 协议的对象。就是一个对象,一个重写了object类中enter() 和 exit() 方法的对象。这样的对象被称为上下文管理器Content Manager,它可以使用with关键字进行操作这个对象。
3.1 with语句使用的格式
with expression [as variable]:
with-block
----------------------------------------------------------------------------
案例1:
with open("aaaa.txt","w+") as f : #open方法的返回值给变量f,相当于起个别名。
f.write("hahhahaha")
这里expression是一个表达式,该表达式的结果必须是一个支持上下文管理协议的对象(即具有enter()和exit()方法的对象),比如案例中操作文件就是用的open("aaaa.txt","w+"),这个表达式返回的结果就是file对象。具体返回的结果根据打开模式不同,返回值也不同,比如当open()用于以文本模式('w')打开文件时,它返回一个TextIOWrapper。那么这么说,既然open()可以用with进行操作,底层必然实现了上下文管理器协议的。查看官网可知。一些标准Python对象现在都支持上下文管理协议,并且可以与with语句一起使用。文件对象就是一个例子。
- 既然支持上下文管理协议,也就是实现底层实现了enter()和exit()方法。对象的enter()方法在with-block执行之前被调用,因此可以在类重写的enter方法里根据实际需求添加特定条件的过滤代码,enter方法。具体使用后面再说。
- 在with-block 块的执行完成后,会调用对象的exit_()方法,即使with-block块执行中引发异常,也可以运行这个exit方法,执行一些特定的操作(类似于try finally,finally无论是否有异常,都会执行其中的代码)。比如文件对象就在这个exit方法里调用了close()函数,这样不管open操作是否异常,文件最终都会关闭。
3.2 自定义一个实现了上下文管理器协议的对象:上下文管理器
根据官网定义很简单,只要该类重写了object类中__enter__()和__exit__()方法即可。
#自定义一个上下文管理器,实现原open()函数的功能
class MyOpen(object):
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
def __enter__(self):
print("entering")
self.f = open(self.filename, self.mode)
return self.f
def __exit__(self, *args):
print("will exit")
self.f.close()
#下面使用自定义的上下文管理器,
#1.首先MyOpen("bbbb.txt","w") 先执行__init__,完成初始化,返回的是一个支持上下文管理器协议的对象给f
#2.然后执行__enter__函数,该函数执行open()方法操作文件,并且返回这个file对象。
#3.根据返回的file对象f,这个时候执行f.f.write("hello ,hahahhah")
#4.不管第三步是否异常,最后都会调用__exit__执行文件的close.
with MyOpen("bbbb.txt","w") as f:
f.write("hello ,hahahhah")
4.实现上线文管理器的其他方式
Python 还提供了一个 contextmanager 的装饰器,更进一步简化了上下文管理器的实现方式。通过 yield 将函数分割成两部分,yield 之前的语句在 enter 方法中执行,yield 之后的语句在 exit 方法中执行。紧跟在 yield 后面的值是函数的返回值。具体详细参考python官网:https://docs.python.org/release/2.6/whatsnew/2.6.html#pep-343-the-with-statement
from contextlib import contextmanager
@contextmanager
def my_open(path, mode):
f = open(path, mode)
yield f
f.close()
使用演示:
with my_open('out.txt', 'w') as f:
f.write("hello , the simplest context manager")
尖叫提示:
Python 提供了 with 语法用于简化资源操作的后续清除操作,是 try/finally 的替代方法,实现原理建立在上下文管理器之上。此外,Python 还提供了一个 contextmanager 装饰器,更进一步简化上下管理器的实现方式。但是实际开中,一般不需要不要开发者掌握with的实现原理,会使用即可哈。