首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Python 的 with 到底在干啥?上下文管理器原理拆解,3 个场景看明白

Python 的 with 到底在干啥?上下文管理器原理拆解,3 个场景看明白

原创
作者头像
小小张说故事
发布于 2026-10-10 17:12:43
发布于 2026-10-10 17:12:43
140
举报

结论先行:Python 的 with 不是语法糖,而是一套"自动收尾"协议——只要一个对象实现了 __enter__ 和 __exit__ 两个方法,就能进 with 块时自动"开门"、出块时不管有没有报错都自动"关门"。它解决的只有一个问题:资源别漏关。文件、锁、数据库连接、事务,凡是"用完了必须还原现场"的东西,**with** 都是最稳的写法。


1. 最常见的误用:忘了关文件

新手常写:

代码语言:python
复制
f = open('data.txt')
data = f.read()
f.close()                 # 万一上面报错,这行永远不执行 → 文件句柄泄漏

问题:read() 抛异常,close() 就被跳过,文件句柄一直占着。改成 with:

代码语言:python
复制
with open('data.txt') as f:
    data = f.read()
# 无论块内是否报错,文件都自动关闭

原理:open() 返回的文件对象实现了 __enter__(返回文件自身)和 __exit__(负责关文件)。进块调 __enter__,出块调 __exit__,异常也照常调。

2. 自己写一个上下文管理器

代码语言:python
复制
class Timer:
    def __enter__(self):
        import time
        self.start = time.time()
        return self
    def __exit__(self, exc_type, exc_val, tb):
        print(f"耗时 {time.time() - self.start:.3f}s")
        return False          # False=不吞掉异常,继续向外抛

with Timer():
    sum(range(10**7))

要点:__exit__ 的三个参数是异常信息(exc_type/exc_val/tb),都是 None 表示正常退出;返回 True 会"吞掉"异常,返回 False 则放行。绝大多数情况返回 False。

3. 更省事的写法:@contextmanager

每次手写类太重,标准库给你语法糖:

代码语言:python
复制
from contextlib import contextmanager

@contextmanager
def tag(name):
    print(f"<{name}>")
    yield                  # yield 前的算 __enter__,后的算 __exit__
    print(f"</{name}>")

with tag("div"):
    print("hello")
# 输出:<div>  hello  </div>

为什么好用:yield 把函数劈成两半——前面是进门动作,后面是收尾动作。比写类清晰太多。

4. 真实场景:锁和数据库事务

代码语言:python
复制
import threading
lock = threading.Lock()
with lock:                 # 进块自动 acquire,出块自动 release,绝不死锁
    # 临界区
    ...

# 数据库:要么全提交,要么全回滚
with connection:           # 出块自动 commit/rollback
    cursor.execute("INSERT ...")

价值:锁忘了 release 会死锁,事务忘了 commit/rollback 会悬挂连接。with 把"还原现场"变成编译器级别的保证。

5. 一个反直觉的点:with 块里的异常会进 exit

代码语言:python
复制
class Demo:
    def __enter__(self): return self
    def __exit__(self, t, v, tb):
        print("exit, exc=", t)
        return False

with Demo():
    raise ValueError("boom")
# 先打印 exit, exc=ValueError,再向外抛

记住:__exit__ 是"善后"不是"拦截"。想静默吞异常才返回 True,否则异常照样抛。


对照表:with 能管什么

资源

不用 with 的隐患

with 怎么救

文件

句柄泄漏

自动 close()

锁

死锁

自动 release()

DB 连接

连接悬挂

自动 commit/rollback

临时改状态

忘了还原

__exit__ 还原


排错清单(收藏级)

  • AttributeError: __enter__:你 with 的对象不是上下文管理器(没实现协议),检查是不是把普通对象套进去了。
  • 异常被莫名吞掉:某个 __exit__ 返回了 True,把它改回 False。
  • 想同时管多个资源:with A() as a, B() as b: 一行叠多个,Python 支持。
  • yield 后还想处理异常:@contextmanager 里 try/finally 包住 yield 即可。
  • 别滥用:纯计算、没有"资源要还原"的场景,用 with 反而增加心智负担。

一句话记住:with 就是 Python 的"自动关门器"——进块开门、出块关门,不管中途炸不炸,门一定关。凡是"用完必须还原"的东西,无脑用 with。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 1. 最常见的误用:忘了关文件
  • 2. 自己写一个上下文管理器
  • 3. 更省事的写法:@contextmanager
  • 4. 真实场景:锁和数据库事务
  • 5. 一个反直觉的点:with 块里的异常会进 exit
  • 对照表:with 能管什么
  • 排错清单(收藏级)
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档