python中的with关键字原理详解

对于系统资源如文件、数据库连接、socket 而言,应用程序打开这些资源并执行完业务逻辑之后,必须做的一件事就是要关闭(释放)该资源。
在代码中经常会看见 with open(file) as f 对文件进行操作,其中 with 关键字到底有什么用处呢?让我们一起来看看如何正确关闭一个文件。

1.普通版
def fun1():
f = open(“test.txt”, “w”)
f.write(“hello python”)
f.close

这样写有一个潜在的问题,如果在调用 write 的过程中,出现了异常进而导致后续代码无法继续执行,close 方法无法被正常调用,因此资源就会一直被该程序占用者释放。那么该如何改进代码呢?

2.进阶版
def fun2():
try:
f = open(“test.txt”, “w”)
f.write(“hello python”)
except Exception as e:
print(e)
finally:
f.close()

改良版本的程序是对可能发生异常的代码处进行 try 捕获,使用 try/finally 语句,该语句表示如果在 try代码块中程序出现了异常,后续代码就不再执行,而直接跳转到 except 代码块。而无论如何,finally 块的代码*终都会被执行。因此,只要把 close 放在 finally 代码中,文件就一定会关闭。

3.高级版
def fun3():
with open(“test.txt”, “w”) as f:
f.write(“hello python”)

一种更加简洁、优雅的方式就是用with关键字。open方法的返回值赋值给变量f,当离开with代码块的时候,系统会自动调用 f.close() 方法,with 的作用和使用 try/finally 语句是一样的。那么它的实现原理是什么?在讲with的原理前要涉及到另外一个概念,就是上下文管理器(Context Manager)。
什么是上下文?
上下文在不同的地方表示不同的含义,要感性理解。在编程中 context 上下文其实说白了就是环境。
例如 一个 APP 应用,在切换界面的时候,要保存你是在哪个屏幕跳过来的等等信息,以便你点击返回的时候能正确跳回,如果不存肯定就无法正确跳回了。
再比如线程、协程进行任务切换时,程序怎么能知道切换到另一个任务,是从头开始执行还是从中间呢?其上下文就起到作用,就是任务本身会对其环境进行保存,做到哪里了,做了多少,各种状态都会标识记录,从而形成了上下文环境,因此在切换时根据每个任务的上下文环境,继续执行,从而达到多任务。
上下文管理器
任何类实现了__enter__()和__exit__()方法的对象都可称之为上下文管理器。
上下文管理器对象可以使用with关键字。

4.用类还原with的实现原理
class Fun4(object):
def __init__(self, file_name, mode):
self.file_name = file_name
self.mode = mode
def __enter__(self):
self.f = open(self.file_name, self.mode)
return self.f
def __exit__(self,*args):
self.f.close()
with Fun4(“test.txt”, “w”) as f:
f.write(“hello python”)
“””
首先Test4(“1.txt”, “w”)初始化实例对象,
然后with会寻找类中是否有__enter__和__exit__,
如果有则调用__enter__函数,
*后__enter__()方法返回资源对象,这里就是你将要打开
的那个文件对象,__exit__()方法处理一些清除工作。
“””

5.使用contextmanager装饰器,实现with功能
from contextlib import contextmanager
“””
Python还提供了一个contextmanager的装饰器,更进一步简化
了上下文管理器的实现方式。通过yield将函数分割成两部分,yield之前的
语句在__enter__方法中执行,yield之后的语句在__exit__方法中执行。
紧跟在yield后面的值是函数的返回值。
“””
@contextmanager
def fun5(path, mode):
f = open(path, mode)
yield f
f.close()
with fun5(“test.txt”, “w”) as f:
f.write(“hello python”)

总结:
Python 提供了 with 语法用于简化资源操作的后续清除操作,是 try/finally 的替代方法,实现原理建立在上下文管理器之上。此外,Python 还提供了一个 contextmanager 装饰器,更进一步简化上下管理器的实现方式。