分析用Python脚本关闭文件操作的机制


Posted in Python onJune 28, 2015

如果不用“with”,那么Python会在何时关闭文件呢?答案是:视情况而定。

Python程序员最初学到的东西里有一点就是可以通过迭代法很容易地遍历一个打开文件的全文:

f = open('/etc/passwd')
for line in f:
  print(line)

注意上面的代码具有可行性,因为我们的文件对象“f”是一个迭代器。换句话说,“f“ 知道在一个循环或者任何其他的迭代上下文中做什么,比如像列表解析。

我的Python课堂上的大多数学生都具有其他编程语言背景,在使用以前所熟悉的语言时,他们总是在完成文件操作时被期望关闭文件。因此,在我向他们介绍了Python文件操作的内容不久后他们问起如何在Python中关闭文件时,我一点都不惊讶。

最简单的回答就是我们可以通过调用f.close()显式地关闭文件。一旦我们关闭了文件,该文件对象依然存在,但是我们无法再通过它来读取文件内容了,而且文件对象返回的可打印内容也表明文件已经被关闭。

>>> f = open('/etc/passwd')
>>> f
<open file '/etc/passwd', mode 'r' at 0x10f023270>
>>> f.read(5)
'##n# '
 
f.close()
>>> f
<closed file '/etc/passwd', mode 'r' at 0x10f023270>
 
f.read(5)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-11-ef8add6ff846> in <module>()
----> 1 f.read(5)
ValueError: I/O operation on closed file

所以是这样,我在用Python编程的时候,很少明确地对文件调用 “close” 方法。此外,你也很可能不想或不必那样做。

打开文件的优选最佳实践方式是使用 “with” 语句,就像如下所示:

with open('/etc/passwd') as f:
  for line in f:
    print(line)

“with”语句对 “f” 文件对象调用在Python中称作“上下文管理器”的方法。也就是说,它指定 “f” 为指向 /etc/passwd 内容的新的文件实例。在 “with” 打开的代码块内,文件是打开的,而且可以自由读取。

然而,一旦Python代码从 “with” 负责的代码段退出,文件会自动关闭。试图在我们退出 “with”代码块后从 f 中读取内容会导致和上文一样的 ValueError 异常。所以,通过使用 “with”,你避免了显式地关闭文件的操作。Python 会以一种不那么有 Python 风格的方式在幕后神奇而静静地替你关闭文件。

但是你不显式地关闭文件会怎样?如果你有点懒,既不使用 “with” 代码块也不调用f.close()怎么办?这时文件会什么时候关闭?何时应该关闭文件?

我之所以问这个,是因为我教了这么多年Python,确信努力教授“with”或上下文管理器的同时又教很多其它的话题超出了学生接受的范围。在介绍性课程谈及 “with” 时,我一般会告诉学生在他们职业生涯中遇到这个问题时,让Python去关闭文件就好,不论文件对象的应用计数降为0还是Python退出时。

在我的Python文件操作免费e-mail课程中,我并没有在所有的解决方案中使用with,想看看如何。结果一些人质疑我,说不使用“with”会向人们展示一种糟糕的实践方案并且会有数据未写入磁盘的风险。

我收到了很多关于此话题的邮件,于是我问自己:如果我们没有显式地关闭文件或者没用“with”代码块,那么Python会何时关闭文件?也就是说,如果我让文件自动关闭,那么会发生什么?

我总是假定当对象的引用计数降为0时,Python会关闭文件,进而垃圾回收机制清理文件对象。当我们读文件时很难证明或核实这一点,但写入文件时却很容易。这是因为当写入文件时,内容并不会立即刷新到磁盘(除非你向“open”方法的第三个可选参数传入“False”),只有当文件关闭时才会刷新。

于是我决定做些实验以便更好地理解Python到底能自动地为我做什么。我的实验包括打开一个文件、写入数据、删除引用和退出Python。我很好奇数据是什么时候会被写入,如果有的话。

我的实验是这个样子:

f = open('/tmp/output', 'w')
f.write('abcn')
f.write('defn')
# check contents of /tmp/output (1)
del(f)
# check contents of /tmp/output (2)
# exit from Python
# check contents of /tmp/output (3)

我在Mac平台上用Python 2.7.9 做了第一个实验,报告显示在阶段一文件存在但是是空的,阶段二和阶段三中文件包含所有的内容。这样,在CPython 2.7中我最初的直觉似乎是正确的:当一个文件对象被垃圾回收时,它的 __del__ (或者等价的)方法会刷新并关闭文件。而且在我的IPython进程中调用“lsof”命令显示文件确实在引用对象移除后被关闭了。

那 Python3 如何呢?我在Mac上 Python 3.4.2 环境下做了以上的实验,得到了相同的结果。移除对文件对象最后的引用后会导致文件被刷新并且被关闭。

这对于 Python 2.7 和 3.4 很好。但是在 PyPy 和 Jython下的替代实现会怎样呢?或许情况会有些不同。

于是我在 PyPy 2.7.8 下做了相同的实验。而这次,我得到了不同的结果!删除文件对象的引用后——也就是在阶段2,并没有导致文件内容被刷入磁盘。我不得不假设这和垃圾回收机制的不同或其他在 PyPy 和 CPython中工作机制的不同有关系。但是如果你在 PyPy中运行程序,就绝不要指望仅仅因为文件对象的引用结束,文件就会被刷新和关闭。命令 lsof 显示直到Python进程退出时文件才会被释放。

为了好玩,我决定尝试一下 Jython 2.7b3. 结果Jython 表现出了和PyPy一样的行为。也就是说,从 Python 退出确实会确保缓存中的数据写入磁盘。

我重做了这些实验,但是我把 “abcn”和 “defn”换成了 “abcn”*1000 和“defn”*1000.

在 Python 2.7 的环境下,“abcn” * 1000 语句执行后没有任何东西写入。但“defn” * 1000 语句执行后,文件包含有4096个字节——可能代表缓冲区的大小。调用 del(f) 删除文件对象的引用导致数据被刷入磁盘和文件关闭,此时文件中共有8000字节的数据。所以忽略字符串大小的话 Python 2.7 的行为表现基本相同。唯一不同的是如果超出了缓冲区的大小,那么一些数据将在最后文件关闭数据刷新前写入磁盘。

换做是Python 3的话,情况就有些不同了。f.write执行后没有任何数据会写入。但是文件对象引用一旦结束,文件就会刷新并关闭。这可能是缓冲区很大的缘故。但毫无疑问,删除文件对象引用会使文件刷新并关闭。

至于 PyPy 和 Jython,对大文件和小文件的操作结果都一样:文件在 PyPy 或 Jython 进程结束的时候刷新并关闭,而不是在文件对象的引用结束的时候。

为了再次确认,我又使用 “with” 进行了实验。在所有情况下,我们都能够轻松的预测文件是何时被刷新和关闭的——就是当退出代码段,并且上下文管理器在后台调用合适方法的时候。

换句话说,如果你不使用“with”,那么至少在非常简单的情形下,你的数据不一定有丢失的危险。然而你还是不能确定数据到底是在文件对象引用结束还是程序退出的时候被保存的。如果你假定因为对文件唯一的引用是一个本地变量所以文件在函数返回时会关闭,那么事实一定会让你感到吃惊。如果你有多个进程或线程同时对一个文件进行写操作,那么你真的要非常小心了。

或许这个行为可以更好地定义不就可以在不同的平台上表现得基本一致了吗?也许我们甚至可以看看Python规范的开始,而不是指着CPython说“Yeah,不管版本如何总是对的”。

我依然觉得“with”和上下文管理器很棒。而且我想对于Python新手,理解“with”的工作原理很难。但我还是不得不提醒新手开发者注意:如果他们决定使用Python的其他可选版本,那么会出现很多不同于CPython的古怪情况而且如果他们不够小心,甚至会深受其害。

Python 相关文章推荐
python文件读写操作与linux shell变量命令交互执行的方法
Jan 14 Python
Python彩色化Linux的命令行终端界面的代码实例分享
Jul 02 Python
TensorFlow在MAC环境下的安装及环境搭建
Nov 14 Python
Python下载网络小说实例代码
Feb 03 Python
Python字符串通过'+'和join函数拼接新字符串的性能测试比较
Mar 05 Python
如何用C代码给Python写扩展库(Cython)
May 17 Python
浅谈Python中函数的定义及其调用方法
Jul 19 Python
django如何自己创建一个中间件
Jul 24 Python
解决Atom安装Hydrogen无法运行python3的问题
Aug 28 Python
python 实现return返回多个值
Nov 19 Python
使用Pyhton集合set()实现成果查漏的例子
Nov 24 Python
教你使用pyinstaller打包Python教程
May 27 Python
python实现linux下使用xcopy的方法
Jun 28 #Python
自动化Nginx服务器的反向代理的配置方法
Jun 28 #Python
python读取TXT到数组及列表去重后按原来顺序排序的方法
Jun 26 #Python
在Python中使用zlib模块进行数据压缩的教程
Jun 26 #Python
Python中的zipfile模块使用详解
Jun 25 #Python
Python实现保证只能运行一个脚本实例
Jun 24 #Python
Python中的默认参数详解
Jun 24 #Python
You might like
使用PHP获取网络文件的实现代码
2010/01/01 PHP
php的urlencode()URL编码函数浅析
2011/08/09 PHP
PHP Echo字符串的连接格式
2016/03/07 PHP
PHP中十六进制颜色与RGB颜色值互转的方法
2019/03/18 PHP
Javascript操纵Cookie实现购物车程序
2006/11/23 Javascript
JQuery 学习笔记 选择器之二
2009/07/23 Javascript
$.getJSON在IE下失效的原因分析及解决方法
2013/06/16 Javascript
使用jquery hover事件实现表格的隔行换色功能示例
2013/09/03 Javascript
javascript实现可拖动变色并关闭层窗口实例
2015/05/15 Javascript
在JavaScript中处理数组之reverse()方法的使用
2015/06/09 Javascript
JavaScript节点及列表操作实例小结
2015/08/05 Javascript
EasyUI闪屏EasyUI页面加载提示(原理+代码+效果图)
2016/02/21 Javascript
基于jQuery的网页影音播放器jPlayer的基本使用教程
2016/03/08 Javascript
jquery实现跳到底部,回到顶部效果的简单实例(类似锚)
2016/07/10 Javascript
javascript中sort排序实例详解
2016/07/24 Javascript
jQuery中hover方法搭配css的hover选择器,实现选中元素突出显示方法
2017/05/08 jQuery
PHP实现记录代码运行时间封装类实例教程
2017/05/08 Javascript
详解 vue.js用法和特性
2017/10/15 Javascript
浅谈webpack 自动刷新与解析
2018/04/09 Javascript
Angular value与ngValue区别详解
2019/11/27 Javascript
[52:05]EG vs OG 2019国际邀请赛小组赛 BO2 第二场 8.16
2019/08/18 DOTA
python判断、获取一张图片主色调的2个实例
2014/04/10 Python
Python中的tuple元组详细介绍
2015/02/02 Python
深入解答关于Python的11道基本面试题
2017/04/01 Python
python实现两张图片拼接为一张图片并保存
2019/07/16 Python
Python入门Anaconda和Pycharm的安装和配置详解
2019/07/16 Python
python+Django实现防止SQL注入的办法
2019/10/31 Python
解析PyCharm Python运行权限问题
2020/01/08 Python
Python中关于logging模块的学习笔记
2020/06/03 Python
Expedia加拿大官方网站:加拿大最大的在线旅游提供商
2017/12/31 全球购物
介绍一下Ruby的特点
2013/01/20 面试题
教师师德师风整改措施
2014/10/24 职场文书
2014年后勤工作总结范文
2014/12/16 职场文书
个人廉政承诺书
2015/04/28 职场文书
2019奶茶店创业计划书范本,值得你借鉴
2019/08/14 职场文书
利用Python判断你的密码难度等级
2021/06/02 Python