【Python 基础篇】Python 上下文管理器 with...as...

上下文管理器在Python中用于自动管理资源的开启和关闭,例如文件操作、数据库连接等。它通过`__enter__`和`__exit__`方法定义对象的生命周期。with语句简化了资源管理的代码,确保在操作完成后资源总会被正确关闭,避免资源泄露。文章展示了如何自定义上下文管理器以及其工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        Python 2.5 就引入了上下文管理器(context manager),是一个便捷性的操作,它可以划定某个对象的使用范围。

前文

        上下文管理器顾名思义是用于管理上下文(即环境)的,我们的操作代码在中间,中间的代码执行需要之前的一些资源引入和之后资源清理关闭等操作,就如冲锋和垫后。

为什么要引入上下文管理器呢?上常见的场景是:

  • 打开一个文件,然后进行操作,操作完成后关闭文件资源
  • 连接一个数据库,然后进行操作,操作完成后关闭连接
  • 调用打开摄像头,然后拍照,操作完成后关闭
  • 连接服务器,然后获取数据上传数据,操作完成关闭连接
  • 使用其他外设
  • 等等

        模式为上文-操作-下文,因为以上都是基于 IO 等资源操作,往往这些资源都是有限的,如果由于忘记等原因没有及时关闭的话,可能会造成错误。操作系统同一时间能打开的文件数量也是有限的,如果同时打开多个资源处理后又不关闭会造成资源耗尽。

        因此,上下文管理器是指在一段代码执行之前做一些预处理工作;执行之后做一些清理工作,比如上例的几种场景。如同我们看书时,看前打开书,看完合上书。

        Python 引入 with as 语句能让我们用简单的代码实现上下文的管理,省去 try/finally 的繁琐操作,让代码简化。

语法

      在没有引入上下文管理器之前,我们操作一个文件,是这样的:

fd = open('file.txt','w')
fd.write('Hello world!')
fd.close()

        而在引入上下文管理器之后,我们可以这么做:

with open('file.txt','w') as fd:
	fd.write('i am python learner and glad to learn python coding.')

        如果尝试自己实现一个上下文管理器,该怎么做呢?

# 一个简单的文件写入对象
class FileWriter(object):
    def __init__(self, file_name):
        self.file_name = file_name

    def __enter__(self):
        self.file = open(self.file_name, 'w')
        return self.file

    def __exit__(self):
        self.file.close()

# with 使用 FileWriter
with FileWriter('my_file.txt') as f:
    f.write('hello world')

        上下文管理器 是一个对象,它定义了在执行 with 语句时要建立的运行时上下文。 上下文管理器处理进入和退出所需运行时上下文以执行代码块。 通常使用 with 语句(在 with 语句 中描述),但是也可以通过直接调用它们的方法来使用。

上下文管理器的典型用法包括保存和恢复各种全局状态,锁定和解锁资源,关闭打开的文件等等。

with 语句语法有以下几种形式:

with EXPRESSION as TARGET:
    SUITE
# 以及
with A() as a, B() as b:
    SUITE
# 和(3.10 版支持)
with (
    A() as a,
    B() as b,
):
    SUITE
# 后两种相当于
with A() as a:
    with B() as b:
        SUITE

原理

同时包含 __enter__() 和 __exit__() 方法的对象就是上下文管理器。

with 语句的执行过程如下:

  • 对上下文表达式(with 后的表达式)进行求值以获得上下文管理器。
  • 载入上下文管理器的 __enter__() 以便后续使用。
  • 载入上下文管理器的 __exit__() 以便后续使用。
  • 发起调用上下文管理器的 __enter__() 方法。
  • 如果 with 语句中包含一个目标,来自 __enter__() 的返回值将被赋值给它。
    • 注: with 语句会保证如果 __enter__() 方法返回时未发生错误,则 __exit__() 将总是被调用。 因此,如果在对目标列表赋值期间发生错误,则会将其视为在语句体内部发生的错误。 参见下一步。
  • 执行语句体。
  • 发起调用上下文管理器的 __exit__() 方法。 如果语句体的退出是由异常导致的,则其类型、值和回溯信息将被作为参数传递给 __exit__()。 否则的话,将提供三个 None 参数。
    • 如果语句体的退出是由异常导致的,并且来自 __exit__() 方法的返回值为假,则该异常会被重新引发。 如果返回值为真,则该异常会被抑制,并会继续执行 with 语句之后的语句。
    • 如果语句体由于异常以外的任何原因退出,则来自 __exit__() 的返回值会被忽略,并会在该类退出正常的发生位置继续执行。

with as 相当于:

manager = (EXPRESSION)
enter = type(manager).__enter__
exit = type(manager).__exit__
value = enter(manager)
hit_except = False

try:
    TARGET = value
    SUITE
except:
    hit_except = True
    if not exit(manager, *sys.exc_info()):
        raise
finally:
    if not hit_except:
        exit(manager, None, None, None)

内置支持

Python 内置一些内置对象支持了上下文协议:

  • file
  • decimal.Context
  • thread.LockType
  • threading.Lock
  • threading.RLock
  • threading.Condition
  • threading.Semaphore
  • threading.BoundedSemaphore