Python上下文管理器到底有什么魔力

535 阅读5分钟

这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战

作为一个程序员,你一定听说过内存泄露,导致内存泄露的根本原因就在于我们在程序中创建了一个对象,却没有及时的销毁释放掉,这个未被释放的对象就会一直占着内存,直到程序结束。那这样会有什么问题吗?其实量少的话还好,基本不会造成太大影响,但是量大的话,就有可能把内存占满,导致程序被迫停止,这就是内存泄露。所以做好资源管理,是我们在编程中尤为重要的事情,无论处理锁、文件、会话还是数据库连接,我们必须确保使用完毕后关闭并释放这些资源,以便它们正确运行。

通常我们在使用资源的时候会使用try···finally语句块, 在try语句块中使用资源,并在finally语句块中把资源释放。 在Python中,其实有个更简便的解决方式,那就是上下文管理器(context manager),它能够帮助你自动分配并且释放资源,其中最典型的应用就是with语句。

什么是上下文管理器?

说到Python的上下文管理器,有人可能不是很清楚,你可以把它理解为try···finally语句块的替代品,使用with语句来执行,与try···finally一样,引入此模式的目的,也是为了保证在执行某些操作时,即使发生异常情况,也能够释放掉资源。

基本语法

with 上下文管理器 as ···:
    语句体

例如:

with open('test.txt', 'w') as f:
    f.write('tigeriaf')

上面的代码就是上下文管理器使用的基本语法,其中的open('test.txt')就是一个上下文管理器。

其实上下文管理器的协议也很简单,就是在一个类中实现__enter__()__exit__()两个方法,那么这个类的实例就是一个上下文管理器,其中,__enter__(),方法返回需要被管理的资源,__exit__()方法里通常会存在一些释放、清理资源的操作,比如关闭文件、关闭数据库连接等。

with代码中遇到上下文管理器时,将__enter__()方法触发并将其返回值放入as关键字后面的变量中,然后再执行语句体,执行完语句体后,最后执行__exit__()方法来释放、清理资源。

既然上下文管理器这么好用,那我们怎么去实现一个上下文管理器呢?

实现上下文管理器

既然上面我们已经知道了上下文管理器的协议,就是在一个类里,实现了__enter__()__exit__()两个方法,这个类的实例就是一个上下文管理器。

例如这个示例:

class FileManager:
    def __init__(self, file_name, mode):
        print('执行__init__')
        self.file_name = file_name
        self.mode = mode
        self.file = None

    # 实现__enter__方法完成文件的打开操作
    def __enter__(self):
        print('执行__enter__')
        self.file = open(self.file_name, self.mode)
        return self.file

    # 实现__exit__方法完成文件的关闭操作
    def __exit__(self, exc_type, exc_val, exc_tb):
        print('执行__exit__')
        if self.file:
            self.file.close()


# 使用with语句来执行上下文管理器
with FileManager('test.txt', 'w') as f:
    f.write('Hello World!')

下面我们看一下with语句执行上下文管理器的过程,当我们用with语句执行这个上下文管理器时:

  • 方法__init__()被调用,执行FileManager实例的初始化,初始化文件名file_nametest.txt,文件模式mode为w
  • 方法__enter__()被调用,文件test.txt以写入的模式被打开,并且返回FileManager实例,并赋给变量f
  • 字符串Hello World!被写入文件test.txt
  • 方法__exit__()被调用,关闭之前打开的文件流。

所以程序输出为:

执行__init__
执行__enter__
执行写入文件...
执行__exit__

Process finished with exit code 0

现在我们已经了解了上下文管理器是什么,它是如何工作的以及如何实现它。

基于生成器的上下文管理器

在上面,我们探讨了如何使用__enter__()__exit__()方法实现上下文管理器,但我们还可以使用更简单的方式,那就是使用装饰器contextlib.contextmanager

@contextmanager是一个装饰器,可用于编写自包含的上下文管理器功能,因此,我们不需要创建整个类并实现__enter__()__exit__()方法。

from contextlib import contextmanager


@contextmanager
def file_manager(file_name, mode):
    try:
        f = open(file_name, mode)
        yield f
    finally:
        f.close()


with file_manager('test.txt', 'w') as f:
    f.write('Hello World!')

这种是基于生成器去实现的上下文管理器,需要使用装饰器@contextmanager,同样实现了上下文管理器的功能,需要的代码也更少。其中yield之前的代码承担了__enter__()方法的工作,yield本身的作用可以理解为返回某个值,yield之后的代码就是承担了__exit__()方法的工作。

其实,函数file_manager()本质上是一个生成器,当我们执行with语句时,便会打开文件,并返回文件对象f,当with语句执行完后,finally语句块中会执行关闭文件的操作。

总结

通过这篇总结,我们认识了Python的with语句,学习了上下文管理器,以及了解了上下文管理器可以防止错误并减少样板代码,可以使api更安全、更易于使用,那么我们以后就可以在代码中尝试着去使用上下文管理器以及去写自己的上下文管理器了。

最后,感谢女朋友在工作和生活中的包容、理解与支持 !