4 道练习题·预计 30 分钟·做对一题解锁下一段
各位先来看一段代码,猜猜哪里有坑:
f = open('/tmp/diary.txt', 'w')
f.write('今天打卡迟到了三分钟。')
f.close()「这有什么问题?文件打开了、写完了、关掉了,一气呵成。」善于思考的你可能会这么说。
可是,万一 f.write 那一行抛了异常呢?
close 永远不会被执行,文件描述符就这么悬在那儿。一两次没事,可如果是个 Web 服务跑一整天,几千个请求里只要有几十个写文件失败,操作系统的 fd 就会被慢慢耗光,最后报一个看起来八竿子打不着的「Too many open files」。这种 bug,定位起来非常痛苦。
那怎么办?老办法当然是 try/finally:
f = open('/tmp/diary.txt', 'w')
try:
f.write('今天打卡迟到了三分钟。')
finally:
f.close()但这样很啰嗦。Python 早就提供了一个语法糖:
with open('/tmp/diary.txt', 'w') as f:
f.write('今天打卡迟到了三分钟。')一行顶六行,且保证不管中间是否抛异常,文件都会被关掉。
这就是 with 语句——它背后的机制叫上下文管理器(context manager)。
with 后面跟的不是「文件」也不是「连接」,而是一类东西,Python 给它起了个名字,叫上下文管理器。
什么样的对象能算上下文管理器?只要满足两个方法:
__enter__(self):进入 with 块时被调用,返回值赋给 as 后面的变量__exit__(self, exc_type, exc_val, tb):离开 with 块时被调用——无论是正常离开还是异常离开这就是「上下文管理协议」。它的全部规则就这两条,没有第三条。
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 这个语法糖背后。
io.StringIO 是一个「内存里的文件」,也实现了上下文管理器协议。
请用 with 配合 io.StringIO():
with 块里 write 字符串 'Hello'with 块里再 write 字符串 ' World'getvalue() 的结果(Hello World)赋给变量 textprint(text)输出应该是:
Hello World