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


137

在史前时代(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语句可以确保关闭文件...但是为什么文件对象的迭代器协议中没有包含该语句呢?


4
恕我直言,最后一个建议并不比以前更冗长。它只会做更多的工作(确保完成后关闭文件)。
azhrei 2012年

1
@azhrei多一行,所以从客观上讲它比较冗长。
thebjorn

7
我明白了您的意思,但我只是说比较苹果与苹果,您帖子中的倒数第二条建议也需要异常处理代码,以匹配最后一个选项的功能。因此,在实践中它更为冗长。我猜这取决于上下文,实际上,最后两个选项中哪一个最好。
azhrei 2012年

Answers:


227

首选以下原因正是有一个原因:

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

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

在这样的实现中,如果您的代码打开文件的速度比垃圾收集器调用孤立文件句柄上的终结器的速度快,则可能会从OS收到“打开太多文件”错误。通常的解决方法是立即触发GC,但这是一个讨厌的技巧,必须由可能遇到错误的每个函数(包括库中的函数)来完成。什么样的恶梦。

或者,您可以只使用该with块。

奖金问题

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

为什么文件对象的迭代器协议中未包含该代码?

这是有关API设计的主观问题,因此我有两个部分的主观答案。

从直觉上讲,这是错的,因为它使迭代器协议执行两项单独的操作(遍历行关闭文件句柄),并且使外观简单的函数执行两项操作通常不是一个好主意。在这种情况下,感觉特别糟糕,因为迭代器以准功能,基于值的方式与文件内容相关联,但是管理文件句柄是完全独立的任务。对于阅读代码的人来说,将两者无形地压成一个动作是令人惊讶的,这使得推理程序行为变得更加困难。

其他语言基本上得出了相同的结论。Haskell简要调情了所谓的“惰性IO”,它允许您遍历文件并在到达流末尾时自动将其关闭,但是如今,在Haskell和Haskell中几乎普遍不建议使用惰性IO。用户大多转向更明确的资源管理,例如Conduit,其行为更像withPython中的块。

从技术上讲,您可能需要对Python中的文件句柄进行某些操作,如果迭代关闭了文件句柄,这些操作将无法正常工作。例如,假设我需要遍历文件两次:

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

虽然这是一种不太常见的用例,但请考虑以下事实:我可能刚刚将底部的三行代码添加到了原来具有前三行的现有代码库中。如果迭代关闭了该文件,我将无法执行该操作。因此,将迭代和资源管理分开保持可以更轻松地将代码块组合成一个更大的,可运行的Python程序。

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


1
+1是因为它解释了我对操作的评论中的“何时” ;-)
azhrei 2012年

即使使用其他实现,with处理程序也只会给您快速连续地打开数百个文件的程序带来麻烦。大多数程序都可以使用悬挂文件引用来解决问题。除非您将其禁用,否则最终GC将在某个时候启动并清理文件句柄。with确实可以让您省心,因此仍然是最佳做法。
Lie Ryan

1
@DietrichEpp:也许“悬挂文件引用”不是正确的词,我的意思是说文件句柄不再可用,但尚未关闭。无论如何,GC都会在收集文件对象时关闭文件句柄,因此,只要您没有对该文件对象的额外引用并且您不禁用GC并且不会快速打开许多文件继承,由于不关闭文件,您不太可能会“打开太多文件”。
Lie Ryan

1
是的,这正是“如果您的代码打开文件的速度比垃圾收集器在孤立的文件句柄上调用终结器的速度快”的意思。
Dietrich Epp

1
使用更大的原因是,如果不关闭文件,则不一定会立即写入文件。

20

是,

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

是要走的路。

它并不冗长。更安全。


5

如果您被多余的行关闭,则可以使用包装函数,如下所示:

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

2
调用函数xreadlines ..并将其放入名为xreadlines.py的文件中,我们将返回Python 2.1语法:-)
thebjorn 2012年

@thebjorn:也许,但是您引用的Python 2.1示例在备用实现中对于未关闭的文件处理程序并不安全。可以从未关闭的文件处理程序中安全读取Python 2.1文件至少需要5行。
Lie Ryan

-1
f = open('test.txt','r')
for line in f.xreadlines():
    print line
f.close()

5
这并不能真正回答问题
-Thayne
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.