小白学 Python
AI 编程指南
© 2026 小白学 Python · 基于 walter201230/Python 教程
课程目录关于本站联系方式隐私政策GitHub

全部课程

  1. 00为什么学 Python
  2. 01Python 安装与第一个程序
  3. 02基本数据类型与变量
  4. 03List 与 Tuple
  5. 04Dict 与 Set
  6. 05条件与循环
  7. 06函数
  8. 07迭代
  9. 08面向对象
  10. 09模块
  11. 10魔法方法
  12. 11枚举类
  13. 12元类 metaclass
  14. 13线程与进程
  15. 14正则表达式
  16. 15闭包
  17. 16装饰器
  18. 17代码可读性
  19. 18pathlib:现代化路径处理
  20. 19异常处理
  21. 20dataclass
  22. 21上下文管理器(with)
  23. 22async / await
  24. 23pyproject.toml + uv
  25. 24ruff:一站式代码检查
  26. 25pytest:测试框架
  27. 26日志 logging
  28. 27打包发布到 PyPI
  29. 28学完之后
Python 环境加载中…

上下文管理器(with)

4 道练习题·预计 75 分钟·做对一题解锁下一段

完整通读本章 →

教学 01 / 19· 已读

上下文管理器:with 到底在做什么

各位先来看一段代码,猜猜哪里有坑:

Python· 在本地运行
f = open('/tmp/two_drops_of_water.txt', 'w')
f.write('两点水的日记:今天打卡迟到了三分钟。')
f.close()

「这有什么问题?文件打开了、写完了、关掉了,一气呵成。」善于思考的你可能会这么说。

可是,万一 f.write 那一行抛了异常呢?

close 永远不会被执行,文件描述符就这么悬在那儿。一两次没事,可如果是个 Web 服务跑一整天,几千个请求里只要有几十个写文件失败,操作系统的 fd 就会被慢慢耗光,最后报一个看起来八竿子打不着的「Too many open files」。这种 bug,定位起来非常痛苦。

那怎么办?老办法当然是 try/finally :

Python· 在本地运行
f = open('/tmp/two_drops_of_water.txt', 'w')
try:
    f.write('两点水的日记:今天打卡迟到了三分钟。')
finally:
    f.close()

这样写虽然稳,但是丑。一个简单的写文件,外面要套一层 try/finally ,缩进就多一级。如果接下来还要打开第二个文件、第三个文件,缩进会一层一层套下去,很快就会写出「金字塔代码」。

童鞋们一定见过 Python 的另一种写法:

Python· 在本地运行
with open('/tmp/two_drops_of_water.txt', 'w') as f:
    f.write('两点水的日记:今天打卡迟到了三分钟。')

短短两行,没有 try ,没有 finally ,没有手动 close ,但是不管 write 是否抛异常,文件都会被正确关闭。

这个 with 到底是个什么东西?为什么它能做到这么神奇的事情?这一章我们就把它从里到外讲清楚。

教学 02 / 19

with 的本质:上下文管理协议

with 后面跟的不是「文件」,也不是「连接」、「锁」之类的具体东西,而是一类东西,Python 给它起了个名字,叫「上下文管理器」(context manager)。

什么样的对象能算上下文管理器?只要满足两个方法:

  • __enter__(self) :进入 with 块的时候被调用,返回值会赋给 as 后面的变量
  • __exit__(self, exc_type, exc_val, tb) :离开 with 块的时候被调用,无论是正常离开还是异常离开

这就是「上下文管理协议」(context manager protocol)。它的全部规则就这两条,没有第三条。

我们可以拿打卡这个老朋友来手写一个最简单的上下文管理器,让各位先有个直观感受:

Python· 可在网页运行
class Punch:
    def __enter__(self):
        print('两点水进入打卡区')
        return self

    def __exit__(self, exc_type, exc_val, tb):
        print('两点水离开打卡区')


with Punch() as p:
    print('两点水正在工位摸鱼')

输出:

文本
两点水进入打卡区
两点水正在工位摸鱼
两点水离开打卡区

各位看到了什么?with Punch() as p 这一行做了三件事:

  1. 创建一个 Punch() 实例
  2. 调用这个实例的 __enter__() 方法
  3. 把 __enter__() 的返回值赋给 p

然后才执行 with 块里面的代码。等 with 块结束(不管是正常结束还是异常结束),就调用 __exit__() 。

这个流程看着是不是很眼熟?没错,它本质上就是把:

Python· 结合前文阅读
p_obj = Punch()
p = p_obj.__enter__()
try:
    print('两点水正在工位摸鱼')
finally:
    p_obj.__exit__(None, None, None)

这一坨样板代码,藏到了 with 这个语法糖背后。with 一行顶六行,就是这么来的。

练习 1 / 4·用 with 操作 StringIO题目有问题?

io.StringIO 是一个「内存里的文件」,也实现了上下文管理器协议。

请用 with 配合 io.StringIO():

  1. 在 with 块里 write 字符串 'Hello'
  2. 在 with 块里再 write 字符串 ' World'
  3. 把 getvalue() 的结果(Hello World)赋给变量 text
  4. print(text)

输出应该是:

文本
Hello World
main.py
可编辑
🔒做对当前题解锁下一段 ·0/4

推荐一个,全网最全最友好的中文 AI 编程指南: coding.stormzhang.ai

← 上一章20 · dataclass
上下文管理器(with)
下一章 →22 · async / await
本章目录

上下文管理器(with)

  1. 教学 01上下文管理器:with 到底在做什么
  2. 教学 02with 的本质:上下文管理协议
  3. 练习 1用 with 操作 StringIO
  4. 教学 03 🔒顺手回头看一眼 open
  5. 教学 04 🔒一个稍微有用一点的例子:打卡上下文
  6. 练习 2 🔒自定义上下文管理器类
  7. 教学 05 🔒同时管理多个上下文
  8. 教学 06 🔒__exit__ 的三个参数:异常处理
  9. 教学 07 🔒contextlib:标准库的好朋友
  10. 练习 3 🔒用 @contextmanager 写一个 banner
  11. 教学 08 🔒@contextmanager + 异常处理
  12. 教学 09 🔒contextlib 里的几个常客
  13. 教学 10 🔒suppress:优雅地忽略异常
  14. 练习 4 🔒用 contextlib.suppress 忽略异常
  15. 教学 11 🔒实战:临时改环境变量
  16. 教学 12 🔒临时切换工作目录
  17. 教学 13 🔒ExitStack:动态数量的上下文
  18. 教学 14 🔒async with:异步世界的 with
  19. 教学 15 🔒小实战:写一个 timer 上下文
  20. 教学 16 🔒一个常见的反模式
  21. 教学 17 🔒一个综合的小例子:可重用的「水哥打卡审计器」
  22. 教学 18 🔒小结
  23. 教学 19 🔒结果留下来,资源关起来

学习目录