如何打开带有打开文件句柄的Python生成器

时间:2010-09-03 16:33:13

标签: python generator break

我正在编写一个看起来像“cat”的Python生成器。我的具体用例是“grep like”操作。我希望它能够在满足条件时突破发电机:

summary={}
for fn in cat("filelist.dat"):
    for line in cat(fn):
        if line.startswith("FOO"):
            summary[fn] = line
            break

因此,当break发生时,我需要cat()生成器来完成并关闭fn的文件句柄。

我必须读取包含30 GB总数据的100k文件,FOO关键字出现在标题区域中,因此在这种情况下,cat()函数尽快停止读取文件很重要

还有其他方法可以解决这个问题,但我仍然有兴趣知道如何从具有打开文件句柄的生成器中提前退出。也许Python立即清理它们并在生成器被垃圾收集时关闭它们?

谢谢,

伊恩

4 个答案:

答案 0 :(得分:5)

生成器使用close方法在GeneratorExit语句处引发yield。如果您专门捕获此异常,则可以运行一些拆卸代码:

import contextlib
with contextlib.closing( cat( fn ) ):
    ...

然后在cat

try:
    ...
except GeneratorExit:
    # close the file

如果您想要一种更简单的方法(不在生成器上使用神秘的close方法),只需使cat采用类似文件的对象而不是要打开的字符串,并且自己处理文件IO:

for filename in filenames:
    with open( filename ) as theFile:
        for line in cat( theFile ):
            ...

但是,您基本上不需要担心任何这一点,因为垃圾收集将处理所有这些。尽管如此,

  

显式优于隐式

答案 1 :(得分:5)

通过在同一个对象中实现context protocoliterator protocol,您可以编写非常甜蜜的代码:

with cat("/etc/passwd") as lines:
    for line in lines:
        if "mail" in line:
            print line.strip()
            break

这是一个示例实现,在Linux机器上使用Python 2.5进行了测试。它会读取/etc/passwd的行,直到找到用户audio的行,然后停止:

from __future__ import with_statement


class cat(object):

    def __init__(self, fname):
        self.fname = fname

    def __enter__(self):
        print "[Opening file %s]" % (self.fname,)
        self.file_obj = open(self.fname, "rt")
        return self

    def __exit__(self, *exc_info):
        print "[Closing file %s]" % (self.fname,)
        self.file_obj.close()

    def __iter__(self):
        return self

    def next(self):
        line = self.file_obj.next().strip()
        print "[Read: %s]" % (line,)
        return line


def main():
    with cat("/etc/passwd") as lines:
        for line in lines:
            if "mail" in line:
                print line.strip()
                break


if __name__ == "__main__":
    import sys
    sys.exit(main())

甚至更简单:

with open("/etc/passwd", "rt") as f:
    for line in f:
        if "mail" in line:
            break

文件对象实现迭代器协议(参见http://docs.python.org/library/stdtypes.html#file-objects

答案 2 :(得分:1)

请同时考虑这个例子:

def itertest():
    try:
        for i in xrange(1000):
            print i
            yield i
    finally:
        print 'finally'

x = itertest()

for i in x:
    if i > 2:
        break

print 'del x'
del x

print 'exit'

0
1
2
3
del x
finally
exit

它表明最终在清理迭代器后运行。我认为__del__(self)正在调用self.close(),请参阅此处:https://docs.python.org/2.7/reference/expressions.html#generator.close

答案 3 :(得分:0)

使用try..finally似乎还有另一种可能性(在Python 2.7.6上测试):

def gen():
    i = 0
    try:
        while True:
            print 'yield %i' % i
            yield i
            i += 1
        print 'will never get here'
    finally:
        print 'done'

for i in gen():
    if i > 1:
        print 'break'
        break
    print i

给我以下打印输出:

yield 0
0
yield 1
1
yield 2
break
done