经常地,程序在开始执行某项任务需要从文件读取数据。在任务完成后数据得到更新,新的数据会覆写到之前读取的文件中。怎么将数据写回到文件呢?一个直觉的方案是:
with open(datafile, 'w') as f: f.write(data)
在通常情况下,它能够正确地完成写回数据的任务。如果出于某种原因文件打开失败,通常也不会有人忘记处理。但是,当写入操作失败了呢?
时不时地编译程序看到 gcc 大把地警告:
警告:忽略声明有 warn_unused_result 属性的‘write’的返回值 [-Wunused-result]
在 Python 中,写文件时如果失败会抛出异常,上层的异常处理机制似乎能够作出相应的应对。但是,真的尽力了吗?
我也曾以为这样不会出问题。直到有一天,本地信箱里出现了这样的错误信息:
OSError: [Errno 28] No space left on device
可能是由于内核的某个 bug,我本来就所剩无几的 /home 分区没空闲空间了。一个 cronjob 在写回数据时发生异常。于是,新的数据没能写入文件。那旧数据呢?因为是以「写」方式打开文件,所以它也没了……
在那次事件之后,那段写回数据的代码变成了这个样子:
with open(datafile + '.tmp', 'w') as f: f.write(t) # if the above write failed (because disk is full), the old data should be kept os.rename(datafile + '.tmp', datafile)
注意:测试表明不使用with
或者显式地关闭文件的做法是有问题的,即使在 CPython 中。
try: open('/dev/full', 'w').write('abc') except: print('fine.')
在 Python 2.7 中会打印错误信息,Python 3.3.0 中无任何信息。都没有预料中的异常被捕获。
>>> python t.py >>> python2 t.py close failed in file object destructor: IOError: [Errno 28] No space left on device
今天之所以写这个,是因为 Arch Linux CN 的群服务器遇到磁盘配额用尽的问题。XMPP 服务器 Prosody 在写入联系人信息时只写了一小部分,大部分数据丢失。这里有 bug 报告。
2013年7月21日更新:Sublime Text 2 作为商业软件,竟然不仅不采用「新建+重命名」的方式写入文件,而且连写入是否成功都不检查。难怪 Linux 版中文输入法的问题迟迟不修复,原来连造成用户数据丢失的问题都无所谓。