with 的引入

一般情况下,在 python 中我们使用

f = open('filename.txt','w')
f.write('hello,world\n')
f.close()

打开文件,并写入,之后关闭。但是这样写会有一个潜在的问题,如果在调用 write 的过程中,出现了异常,比如写一个列表到文件中,那么就会导致后续代码无法继续执行, close 方法就不能被正常调用,因此资源就会被程序一直占用。

我们可以使用 try ... except...finally 来改进上面的代码

f = open('filename.txt', 'w')
try:
    f.write('hello,world\n')
except IOError:
    print('error')
finally:
    f.close()

改进后的程序使用 try 对可能发生一场的代码进行异常捕获,使用 try ... finally 语句,该语句如果在 try 代码块中出现了异常,后续代码就不再执行,而是跳转到对应的 except 代码处。最后不管程序是否发生异常, finally 块都会执行。因次,文件一定会关闭。

with 的使用

相比与使用 try ... finally, 另一种更加简洁和优雅的方式就是使用 with 关键字。

with open('filename.txt','w') as f:
    f.write('hello,world\n')

open 方法的返回值会赋值给变量 f,当离开 with 代码的时候,系统会自动调用 f.close() 方法,with 的作用和使用 try ... finally 语句是一样的。

with 的实现原理

上下文管理器对象可以使用 with 关键字。

上下文管理器

上下文管理器就是实现了 __enter__()__exit__() 方法的对象。上下文管理器对象可以使用 with 关键字。 我们可以通过模拟实现一个自己的文件类,让该类实现 __enter__()__exit__() 方法。

class File():
    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('exiting')
        self.f.close()

__enter__() 方法返回资源对象,这里就是要打开的文件对象, __exit__() 方法进行一些清除工作。因为 File 类实现了上下文管理器,就可以使用 with 语句。

with File('filename.txt','w') as f:
    f.write('hello,world\n')

这样不管是否遇到异常,close 方法都会被调用。

实现上下文管理器的另外方式

python 还可以使用 contextmanager 装饰器,通过 yield 将函数分割成两个部分,yield 之前的语句在__enter__ 方法中执行, yield 之后的语句在 __exit__ 方法中执行。yield后面的值是函数的返回值。

from contextlib import contextmanager

@contextmanager
def my_open(filename, mode):
    f = open(filename, mode)
    yield f
    f.close()

with my_open('filename.txt','w') as f:
    f.write('hello,world\n')

总结

python 提供了 with 语法用来简化资源操作后续的清除操作,是 try...finally 的替代,实现原理是建立在上下文管理器上的。此外 python 还提供了一个 contextmanager 装饰器,进一步简化了上下文管理器的实现。

原创文章,转载请注明出处:https://www.daiyufish.com/article/python3-with-contextmanager/