我应该如何在 Python 中逐行读取文件?

Posted

技术标签:

【中文标题】我应该如何在 Python 中逐行读取文件?【英文标题】:How should I read a file line-by-line in Python? 【发布时间】:2012-07-18 07:41:35 【问题描述】:

在史前时代(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

这是未来的首选方法吗?

[edit] 我知道 with 语句可以确保关闭文件...但是为什么文件对象的迭代器协议中没有包含它?

【问题讨论】:

恕我直言,最后一个建议并不比之前的更冗长。它只是做了更多的工作(确保文件在您完成后关闭)。 @azhrei 多了一行,所以客观上更冗长。 我明白你在说什么,但我只是说比较苹果和苹果,你帖子中的倒数第二个建议也需要异常处理代码以匹配最后一个选项的作用。所以在实践中它更加冗长。我想这取决于上下文最后两个选项中哪个是最好的,真的。 【参考方案1】:

以下是首选的原因之一:

with open('filename.txt') as fp:
    for line in fp:
        print line

我们都被 CPython 用于垃圾收集的相对确定性的引用计数方案宠坏了。其他假设的 Python 实现如果使用其他方案来回收内存,则在没有 with 块的情况下不一定会“足够快地”关闭文件。

在这样的实现中,如果您的代码打开文件的速度快于垃圾收集器在孤立文件句柄上调用终结器的速度,您可能会从操作系统收到“打开的文件过多”错误。通常的解决方法是立即触发 GC,但这是一个讨厌的 hack,必须由可能遇到错误的 每个 函数完成,包括库中的函数。真是一场噩梦。

或者您可以只使用with 块。

奖金问题

(如果只对问题的客观方面感兴趣,请立即停止阅读。)

为什么不包含在文件对象的迭代器协议中?

这是一个关于 API 设计的主观问题,所以我有两个部分的主观答案。

在直觉层面上,这感觉是错误的,因为它使迭代器协议做两件独立的事情——遍历行关闭文件句柄——而且制作一个看起来简单的东西通常是个坏主意函数做两个动作。在这种情况下,感觉特别糟糕,因为迭代器以准功能、基于值的方式与文件内容相关联,但管理文件句柄是一项完全独立的任务。将两者无形地压缩到一个动作中,对于阅读代码的人来说是令人惊讶的,并且使推理程序行为变得更加困难。

其他语言基本上也得出了相同的结论。 Haskell 曾短暂使用过所谓的“惰性 IO”,它允许您遍历文件并在到达流的末尾时自动关闭它,但现在几乎普遍不鼓励在 Haskell 和 Haskell 中使用惰性 IO用户大多转向更明确的资源管理,例如 Conduit,其行为更像 Python 中的 with 块。

在技术层面上,您可能希望对 Python 中的文件句柄执行一些操作,如果迭代关闭文件句柄,这些操作将无法正常工作。例如,假设我需要对文件进行两次迭代:

with open('filename.txt') as fp:
    for line in fp:
        ...
    fp.seek(0)
    for line in fp:
        ...

虽然这是一个不太常见的用例,但请考虑这样一个事实,即我可能刚刚将底部的三行代码添加到最初具有顶部三行的现有代码库中。如果迭代关闭了文件,我将无法做到这一点。因此,将迭代和资源管理分开可以更容易地将代码块组合成一个更大的、工作的 Python 程序。

可组合性是语言或 API 最重要的可用性特性之一。

【讨论】:

+1 因为它解释了我对操作的评论中的“何时”;-) 即使使用替代实现,with 处理程序也只会给您快速连续打开数百个文件的程序带来问题。大多数程序可以毫无问题地使用悬空文件引用。除非您禁用它,否则最终 GC 将在某个时候启动并清除文件句柄。 with 确实让您高枕无忧,因此它仍然是最佳实践。 @DietrichEpp:也许“悬空文件引用”不是正确的词,我真正的意思是不再可访问但尚未关闭的文件句柄。在任何情况下,GC 将在收集文件对象时关闭文件句柄,因此只要您没有对文件对象的额外引用并且您没有禁用 GC 并且您没有快速打开许多文件连续,由于没有关闭文件,您不太可能得到“打开的文件太多”。 是的,这正是我所说的“如果您的代码打开文件的速度比垃圾收集器在孤立文件句柄上调用终结器的速度更快”的意思。 使用 with 的更大原因是,如果你不关闭文件,它不一定会立即被写入。【参考方案2】:

是的,

with open('filename.txt') as fp:
    for line in fp:
        print line

是要走的路。

这不是更冗长。它更安全。

【讨论】:

【参考方案3】:

如果你被多余的行关闭了,你可以像这样使用包装函数:

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 语句会更短:

def with_iter(iterable):
    with iterable as iter:
        yield from iter

【讨论】:

调用函数 xreadlines.. 并将其放入一个名为 xreadlines.py 的文件中,然后我们回到 Python 2.1 语法:-) @thebjorn:也许,但是您引用的 Python 2.1 示例在替代实现中对未关闭的文件处理程序并不安全。从未关闭的文件处理程序中安全读取的 Python 2.1 文件至少需要 5 行。【参考方案4】:
f = open('test.txt','r')
for line in f.xreadlines():
    print line
f.close()

【讨论】:

这并不能真正回答问题

以上是关于我应该如何在 Python 中逐行读取文件?的主要内容,如果未能解决你的问题,请参考以下文章

DELPHI中逐行读取并复制

在 VBA 中逐行读取/解析文本文件

在python中逐行读取一个大的压缩文本文件

如何在 Julia 中逐行读取文件?

在 Go 中逐行读取文件

在python中逐行读取大文件