4 道练习题·预计 75 分钟·做对一题解锁下一段
各位先来看一段代码,猜猜哪里有坑:
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 :
f = open('/tmp/two_drops_of_water.txt', 'w')
try:
f.write('两点水的日记:今天打卡迟到了三分钟。')
finally:
f.close()这样写虽然稳,但是丑。一个简单的写文件,外面要套一层 try/finally ,缩进就多一级。如果接下来还要打开第二个文件、第三个文件,缩进会一层一层套下去,很快就会写出「金字塔代码」。
童鞋们一定见过 Python 的另一种写法:
with open('/tmp/two_drops_of_water.txt', 'w') as f:
f.write('两点水的日记:今天打卡迟到了三分钟。')短短两行,没有 try ,没有 finally ,没有手动 close ,但是不管 write 是否抛异常,文件都会被正确关闭。
这个 with 到底是个什么东西?为什么它能做到这么神奇的事情?这一章我们就把它从里到外讲清楚。
with 后面跟的不是「文件」,也不是「连接」、「锁」之类的具体东西,而是一类东西,Python 给它起了个名字,叫「上下文管理器」(context manager)。
什么样的对象能算上下文管理器?只要满足两个方法:
__enter__(self) :进入 with 块的时候被调用,返回值会赋给 as 后面的变量__exit__(self, exc_type, exc_val, tb) :离开 with 块的时候被调用,无论是正常离开还是异常离开这就是「上下文管理协议」(context manager protocol)。它的全部规则就这两条,没有第三条。
我们可以拿打卡这个老朋友来手写一个最简单的上下文管理器,让各位先有个直观感受:
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 这一行做了三件事:
Punch() 实例__enter__() 方法__enter__() 的返回值赋给 p然后才执行 with 块里面的代码。等 with 块结束(不管是正常结束还是异常结束),就调用 __exit__() 。
这个流程看着是不是很眼熟?没错,它本质上就是把:
p_obj = Punch()
p = p_obj.__enter__()
try:
print('两点水正在工位摸鱼')
finally:
p_obj.__exit__(None, None, None)这一坨样板代码,藏到了 with 这个语法糖背后。with 一行顶六行,就是这么来的。
io.StringIO 是一个「内存里的文件」,也实现了上下文管理器协议。
请用 with 配合 io.StringIO():
with 块里 write 字符串 'Hello'with 块里再 write 字符串 ' World'getvalue() 的结果(Hello World)赋给变量 textprint(text)输出应该是:
Hello World推荐一个,全网最全最友好的中文 AI 编程指南: coding.stormzhang.ai