我应该如何在Python中逐行读取文件?
在史前时期(Python 1.4),我们做了:
fp = open('filename.txt') while 1: line = fp.readline() if not line: break print line
在Python 2.1之后,我们做了:
for line in open('filename.txt').xreadlines(): print line
在Python 2.3中获得方便的迭代器协议之前,可以这样做:
for line in open('filename.txt'): print line
我已经看到一些使用更详细的例子:
with open('filename.txt') as fp: for line in fp: print line
这是前进的首选方法吗?
我得到的是,与语句确保文件closures…但为什么不包括在文件对象的迭代器协议?
为什么以下是首选的原因有一个:
with open('filename.txt') as fp: for line in fp: print line
我们都被CPython相对确定性的垃圾收集引用计数scheme所宠坏。 其他假设的Python实现并不一定会在没有with
块的情况下“足够快地”closures文件with
如果他们使用其他scheme来回收内存的话。
在这样的实现中,如果代码打开文件比垃圾回收器调用孤立文件句柄上的终结器更快,则操作系统可能会出现“文件太多”的错误。 通常的解决方法是立即触发GC,但这是一个令人讨厌的黑客攻击,必须由每个可能遇到错误的函数完成,包括库中的函数。 什么样的恶梦。
或者你可以使用with
块。
奖金问题
(如果只对问题的客观方面感兴趣,就停止阅读。)
为什么不包含在文件对象的迭代器协议中?
这是关于APIdevise的一个主观问题,所以我有两个主观的答案。
在内部层面上,这感觉是错误的,因为它使得迭代器协议做了两件独立的事情 – 遍历行并closures文件句柄 – 使一个简单的函数做两个动作通常是一个坏主意。 在这种情况下,它感觉特别糟糕,因为迭代器以一种准function,基于值的方式与文件内容相关联,但是pipe理文件句柄是一个完全独立的任务。 将两者无形地压缩成一个动作对于阅读代码的人来说是令人惊讶的,并且使得对程序行为的推理变得更加困难。
其他语言基本上也得出了相同的结论。 Haskell简单地调用了所谓的“惰性IO”,它允许你遍历一个文件,并在到达stream的末尾时自动closures它,但是现在在Haskell中几乎不鼓励使用惰性IO,而Haskell用户大多已经转向像Conduit这样更加明确的资源pipe理,其行为更像Python中的block。
在技术层面上,你可能想用Python中的文件句柄来做一些事情,如果迭代closures了文件句柄,那么工作效果就不好。 例如,假设我需要遍历文件两次:
with open('filename.txt') as fp: for line in fp: ... fp.seek(0) for line in fp: ...
虽然这是一个不太常见的用例,但考虑一下这样的事实,即我可能只是将底部的三行代码添加到原来具有前三行的现有代码库中。 如果迭代closures文件,我将无法做到这一点。 因此,保持迭代和资源pipe理分离,可以更容易地将大量代码组合成一个更大的可用的Python程序。
可组合性是语言或API的最重要的可用性function之一。
是,
with open('filename.txt') as fp: for line in fp: print line
是要走的路。
这不是更详细。 这是更安全的。
如果你被多余的行closures,你可以使用如下的包装函数:
def with_iter(iterable): with iterable as iter: for item in iter: yield item for line in with_iter(open('...')): ...
在Python 3.3中, yield from
statement的yield from
将会使得它更短:
def with_iter(iterable): with iterable as iter: yield from iter
f = open('test.txt','r') for line in f.xreadlines(): print line f.close()