> 文章列表 > 简单认识下with和上下文管理器

简单认识下with和上下文管理器

简单认识下with和上下文管理器

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

简单认识下with和上下文管理器

在能够确保程序稳定安全运行的情况下,这样运行没有问题。一旦代码量增多,可能会忘记使用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

简单认识下with和上下文管理器

我们都知道,不管程序在运行try语句中出现了什么问题,都会运行finally中的语句。现在虽然解决了第二个问题,那有没有什么办法能够解决第一个问题呢?这个时候就要提及with了,我们先看看下面一段代码:

def m1():with open("output.txt", "w") as f:f.write("python之禅")print("查看文件是否已关闭:",f.closed)m1()# 结果
# 查看文件是否已关闭: True

简单认识下with和上下文管理器

上面的代码中,我们只是使用了一个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 =filenameself.mode = modedef __enter__(self):print("entering.......")self.f = open(self.filename, self.mode)return self.fdef __exit__(self, *args):self.f.close()print("exit......")with MyFile("output.txt", "w") as f:print("writing")f.write("hello, world!")# 结果
# entering.......
# writing
# exit......

简单认识下with和上下文管理器

contextManager

除了通过__enter__()和__exit__()方法实现上下文管理器,还有一种更加简便的方式,就是装饰器+yield

from contextlib import contextmanager@contextmanager
def my_test(filename, mode):f = open(filename, mode)yield ff.close()with my_test("output.txt", "w") as f:f.write("hello, dear!")

简单认识下with和上下文管理器

当执行f = my_test(“output.txt”, “w”)时,会执行mytest()中的 f = open(filename, mode)语句,遇到yield时,返回f并停止运行。当运行完with中的代码时,猜测是调用了next()方法继续运行mytest上次中断后的代码,即f.close()