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

Ian*_*nSR 4 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
Run Code Online (Sandbox Code Playgroud)

所以当break发生时,我需要cat()生成器完成并关闭文件句柄fn.

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

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

谢谢,

伊恩

Kat*_*iel 5

生成器有一个在语句close中引发的方法.如果您专门捕获此异常,则可以运行一些拆卸代码:GeneratorExityield

import contextlib
with contextlib.closing( cat( fn ) ):
    ...
Run Code Online (Sandbox Code Playgroud)

然后在cat:

try:
    ...
except GeneratorExit:
    # close the file
Run Code Online (Sandbox Code Playgroud)

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

for filename in filenames:
    with open( filename ) as theFile:
        for line in cat( theFile ):
            ...
Run Code Online (Sandbox Code Playgroud)

但是,您基本上不需要担心这一点,因为垃圾收集将处理所有这些.仍然,

显性比隐含更好


Car*_*nte 5

通过在同一个对象中实现上下文协议迭代器协议,您可以编写非常甜蜜的代码,如下所示:

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

这是一个示例实现,在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())
Run Code Online (Sandbox Code Playgroud)

甚至更简单:

with open("/etc/passwd", "rt") as f:
    for line in f:
        if "mail" in line:
            break
Run Code Online (Sandbox Code Playgroud)

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