← 返回题目列表

Python 上下文管理器是什么?with 语句的原理是什么?

高频 中等 第 5 / 27 题 更新于 2026/07/27
上下文管理器with__enter____exit__

简化版

上下文管理器用于管理资源进入和退出逻辑,with 会在进入代码块前调用 __enter__,退出时调用 __exit__。它常用于文件、锁、数据库连接、事务等需要可靠释放资源的场景。

详细版

最常见例子:

with open("a.txt", encoding="utf-8") as f:
    content = f.read()

即使读取过程中发生异常,文件也会在退出 with 时被关闭。

自定义上下文管理器:

class Resource:
    def __enter__(self):
        print("enter")
        return self

    def __exit__(self, exc_type, exc, tb):
        print("exit")
        return False

with Resource() as r:
    print("use")

执行顺序:

enter
use
exit

__exit__ 的三个参数表示异常类型、异常对象和 traceback。如果 __exit__ 返回 True,异常会被吞掉;返回 FalseNone,异常会继续向外传播。

完整版教学

一、为什么需要上下文管理器

很多资源使用都有固定模式:

  1. 获取资源;
  2. 使用资源;
  3. 释放资源。

例如文件:

f = open("a.txt", encoding="utf-8")
try:
    content = f.read()
finally:
    f.close()

如果没有 finally,中间一旦异常,文件可能没被关闭。

with 把这套模板封装起来:

with open("a.txt", encoding="utf-8") as f:
    content = f.read()

代码更短,而且释放更可靠。

二、with 的执行流程

对于:

with manager as value:
    body

可以大致理解为:

mgr = manager
value = mgr.__enter__()
try:
    body
finally:
    mgr.__exit__(exc_type, exc, tb)

真实细节更复杂,但核心就是进入时调用 __enter__,退出时调用 __exit__

as value 接收的是 __enter__ 的返回值,不一定是上下文管理器对象本身。

三、exit 如何处理异常

__exit__ 接收三个异常相关参数:

def __exit__(self, exc_type, exc, tb):
    ...

如果 with 块里没有异常,这三个参数都是 None

如果有异常:

class Demo:
    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc, tb):
        print(exc_type, exc)
        return False

__exit__ 会先执行,然后异常继续抛出。

如果返回 True

def __exit__(self, exc_type, exc, tb):
    return True

异常会被认为已经处理,不再向外传播。这很危险,除非你明确要吞异常。

四、用 contextlib 简化写法

可以用 contextlib.contextmanager 把生成器函数变成上下文管理器:

from contextlib import contextmanager

@contextmanager
def managed_resource():
    print("enter")
    try:
        yield "resource"
    finally:
        print("exit")

使用:

with managed_resource() as r:
    print(r)

yield 前相当于 __enter__yield 后的 finally 相当于 __exit__

五、常见应用场景

上下文管理器常用于:

  • 文件自动关闭;
  • 线程锁自动释放;
  • 数据库连接关闭;
  • 数据库事务提交和回滚;
  • 临时修改环境后恢复;
  • 捕获和管理异常;
  • 性能计时。

例如锁:

with lock:
    shared_data.append(1)

进入时加锁,退出时释放锁,比手写 acquire/release 更不容易漏。

六、设计上下文管理器的注意点

__exit__ 时要特别谨慎:

  • 清理资源应该尽量放在 finally 语义中;
  • 不要无意返回 True 吞掉异常;
  • 释放资源本身如果出错,要考虑日志和异常覆盖问题;
  • __enter__ 返回值要符合调用者预期。

如果资源对象本身就是要被使用的对象,__enter__ 通常返回 self

七、常见误区与追问

记忆钩子:with 的核心不是“少写 finally”,而是把资源进入、退出、异常处理变成一个明确协议:进来走 __enter__,离开一定走 __exit__

  • 误区:with 只适用于文件操作。 文件是最常见例子,但锁、数据库事务、临时目录、网络连接、计时器、环境变量切换都可以用上下文管理器表达资源生命周期。
  • 追问:__exit__ 的三个异常参数分别是什么? 分别是异常类型、异常对象和 traceback;如果 with 块正常结束,三者都是 None
  • 误区:__exit__ 返回什么都不重要。 返回真值会吞掉异常,返回假值或 None 会让异常继续抛出;生产代码里吞异常必须非常明确,否则会掩盖真实故障。
  • 追问:contextlib.contextmanager 为什么函数里要有 yield yield 前相当于 __enter__yield 产出的值给 as 后面的变量,yield 后相当于 __exit__ 的清理逻辑。
  • 误区:用了上下文管理器就一定不会泄漏资源。 前提是 __exit__finally 里真的释放资源;如果清理代码写错,with 只是提供执行时机,不会自动知道怎么释放。
  • 追问:多个上下文管理器嵌套时退出顺序如何? 进入按从左到右或从外到内,退出按相反顺序,类似栈;后进入的资源会先释放。

八、加强记忆

上下文管理器解决的是“进入时准备,退出时清理”的问题。with 前调 __enter__,离开块调 __exit__,异常也会经过 __exit__。记住 __exit__ 返回 True 会吞异常,这个点在面试和工程里都很关键。