Skip to content

上下文管理器

上下文,在读书时主要是指承上启下的意思。而这里的上下文,则是指代码中的环境。上下文管理器就是管理上文与下文的环境。

任何实现了 __enter__() __exit__() 方法的对象都可称之为上下文管理器,上下文管理器对象可以使用 with 关键字。python 语言中,很多内置的对象都是上下文管理器,例如 tkreading.Lock、文件 file 甚至是第三方模块的 requests.get 对象也实现了上下文管理器。

那么文件对象是如何实现这两个方法的呢?我们可以模拟实现一个自己的文件类,让该类实现 __enter__()__exit__() 方法。

python
class File:

    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode

    def __enter__(self):
        print("===进入上下文管理器===")
        self.f = open(self.filename, self.mode)
        return self.f

    def __exit__(self, *args):
        print("===退出上下文管理器===")
        self.f.close()

__enter__() 方法返回资源对象,这里就是你将要打开的那个文件对象,__exit__() 方法处理一些清除工作。

这就是上下文管理协议的一个强大之处,异常可以在__exit__ 进行捕获并由你自己决定如何处理,是抛出呢还是在这里就解决了。在__exit__ 里返回 True(没有return 就默认为 return False),就相当于告诉 Python解释器,这个异常我们已经捕获了,不需要再往外抛了。

在 写__exit__ 函数时,需要注意的事,它必须要有这三个参数:

  • exc_type:异常类型
  • exc_val:异常值
  • exc_tb:异常的错误栈信息

当主逻辑代码没有报异常时,这三个参数将都为 None。

因为 File 类实现了上下文管理器,现在就可以使用 with 语句了。

python
with File('out.txt', 'w') as f:
    print("writing")
    f.write('hello, python')
    f.close()  # 上下文环境退出时会自动调用 __exit__() 方法

这样,你就无需显示地调用 close 方法了,由系统自动去调用,哪怕中间遇到异常 close 方法也会被调用。

在我看来,这和 Python 崇尚的优雅风格有关。

  1. 可以以一种更加优雅的方式,操作(创建/获取/释放)资源,如文件操作、数据库连接;
  2. 可以以一种更加优雅的方式,处理异常;

总结

使用上下文管理器有三个好处:

  1. 提高代码的复用率;
  2. 提高代码的优雅度;
  3. 提高代码的可读性;