在史前时期(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语句确保关闭文件...但为什么不包含在文件对象的迭代器协议中?
答案 0 :(得分:211)
以下是首选的原因之一:
with open('filename.txt') as fp:
for line in fp:
print line
我们都被CPython相对确定的垃圾收集引用计数方案所破坏。如果他们使用其他方案来回收内存,那么其他假设的Python实现不一定会在没有with
块的情况下“足够快”地关闭文件。
在这样的实现中,如果您的代码打开文件的速度比垃圾收集器调用孤立文件句柄上的终结器更快,则可能会从操作系统中获得“打开太多文件”错误。通常的解决方法是立即触发GC,但这是一个讨厌的黑客攻击,它必须由可能遇到错误的每个函数完成,包括库中的错误。真是个噩梦。
或者你可以使用with
阻止。
(如果只对问题的客观方面感兴趣,请立即停止阅读。)
为什么文件对象的迭代器协议中没有包含它?
这是一个关于API设计的主观问题,所以我有两个部分的主观答案。
在内容层面上,这感觉不对,因为它使迭代器协议做了两个单独的事情 - 迭代和行关闭文件句柄 - 并且通常一个简单的看起来是个坏主意功能做两个动作。在这种情况下,感觉特别糟糕,因为迭代器以准功能,基于值的方式与文件内容相关,但管理文件句柄是一项完全独立的任务。将这两者无形地分成一个动作对于阅读代码并使得更难以推理程序行为的人来说是令人惊讶的。
其他语言基本上得出了相同的结论。 Haskell简单地与所谓的“懒惰IO”调情,它允许你迭代一个文件并在你到达流的末尾时自动关闭它,但是现在几乎普遍不鼓励在Haskell中使用惰性IO,而Haskell用户大多转向更明确的资源管理,例如Conduit,其行为更像Python中的with
块。
在技术层面上,你可能想要用Python中的文件句柄做一些事情,如果迭代关闭了文件句柄,它就不会有效。例如,假设我需要迭代文件两次:
with open('filename.txt') as fp:
for line in fp:
...
fp.seek(0)
for line in fp:
...
虽然这是一个不太常见的用例,但考虑一下我可能刚刚在底部将三行代码添加到最初有前三行的现有代码库这一事实。如果迭代关闭了文件,我将无法做到这一点。因此,将迭代和资源管理分开,可以更容易地将代码块组合成更大的,有效的Python程序。
可组合性是语言或API最重要的可用性功能之一。
答案 1 :(得分:19)
是,
with open('filename.txt') as fp:
for line in fp:
print line
是要走的路。
这不是更详细。它更安全。
答案 2 :(得分: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
答案 3 :(得分:-3)
f = open('test.txt','r')
for line in f.xreadlines():
print line
f.close()