with
对于系统资源如文件、数据库连接、socket,应用程序打开这些资源并执行完业务逻辑之后,必须关闭(断开)该资源。系统允许打开的最大文件数量是有限的,如果我们打开文件后没有及时关闭,极端情况下会出现:Too many open files 错误。对于数据库来说,如果没有关闭的连接数量过多,也会出现:Can not connect to MySQL server Too many connections 错误。
我们最开始最基础使用的是以下方式来关闭资源
def m1():
f = open("output.txt", "w")
f.write("python之禅")
f.close()
print("查看文件是否已关闭:",f.closed)
m1()
# 结果
# 查看文件是否已关闭: True
在能够确保程序稳定安全运行的情况下,这样运行没有问题。一旦代码量增多,可能会忘记使用close去关闭文件,而且最大的问题是,如果程序运行在中间代码如f.write(“python之禅”)时出错,那后面就不再运行f.close(),文件也就无法正常关闭。针对第二个问题,我们采用了try…exception…finally…的方式:
def m1():
try:
f = open("output.txt", "w")
f.write("hello world!")
print("查看文件是否已关闭:", f.closed)
except Exception as e:
print(e)
finally:
f.close()
print("查看文件是否已关闭:",f.closed)
m1()
# 结果
# 查看文件是否已关闭: False
# 查看文件是否已关闭: True
我们都知道,不管程序在运行try语句中出现了什么问题,都会运行finally中的语句。现在虽然解决了第二个问题,那有没有什么办法能够解决第一个问题呢?这个时候就要提及with了,我们先看看下面一段代码:
def m1():
with open("output.txt", "w") as f:
f.write("python之禅")
print("查看文件是否已关闭:",f.closed)
m1()
# 结果
# 查看文件是否已关闭: True
上面的代码中,我们只是使用了一个with,并没有看到使用了close(),代码运行完之后,文件就关闭了。实际上它并不是没有运行到close(),而是在with内部语句运行完之后,自动调用了文件类中的__exit__()魔法方法,在这个方法中实现了f.close(),整块代码运行过程如下:
1、运行m1(),调用m1变量指向的代码块
2、运行with open(“output.txt”, “w”) as f,使用关键字with,开启上下文管理,f = open(“output.txt”, “w”),即自动调用文件类中的__enter__()魔法方法,得到self.f
3、运行f.write(“python之禅”)
4、跳出with前,自动运行__exit__()魔法函数,实现self.f.close()关闭文件
5、打印出文件关闭状态
看完上面的流程,想必对with和两个魔法方法、上下文管理器还是一头雾水。那我们就再了解下什么是上下文管理器,with、enter()、exit()和上下文的关系。
上下文管理器
所谓上下文管理器,就是实现了__enter__()和__exit__()两个魔法方法的对象,上下文管理器可以使用with关键字。在遇到with关键字的时候,会自动去调用运行需要处理的对象中的__enter__()魔法方法,在运行完with中的语句时,会自动去调用运行处理完的对象的中的__exit__()魔法方法,前后呼应。
上面的例子中,文件之所以能够使用with,并在使用后自动关闭文件,就是因为文件对象实现了__enter__()魔法方法和__exit__()魔法方法。下面我们来证实下:
class MyFile:
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):
self.f.close()
print("exit......")
with MyFile("output.txt", "w") as f:
print("writing")
f.write("hello, world!")
# 结果
# entering.......
# writing
# exit......
contextManager
除了通过__enter__()和__exit__()方法实现上下文管理器,还有一种更加简便的方式,就是装饰器+yield
from contextlib import contextmanager
@contextmanager
def my_test(filename, mode):
f = open(filename, mode)
yield f
f.close()
with my_test("output.txt", "w") as f:
f.write("hello, dear!")
当执行f = my_test(“output.txt”, “w”)时,会执行mytest()中的 f = open(filename, mode)语句,遇到yield时,返回f并停止运行。当运行完with中的代码时,猜测是调用了next()方法继续运行mytest上次中断后的代码,即f.close()