6

我正在编写一个看起来像“猫”的 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

4 回答 4

6

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

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 ):
            ...

但是,您基本上不需要担心任何这些,因为垃圾收集会处理这一切。仍然,

显式优于隐式

于 2010-09-03T16:39:58.817 回答
5

通过在同一个对象中实现上下文协议迭代器协议,你可以写出这样漂亮的代码:

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

这是一个示例实现,在 Linux 机器上使用 Python 2.5 进行了测试。它读取 的行,/etc/passwd直到找到 user的行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

于 2010-09-03T18:11:17.220 回答
2

也请考虑这个例子:

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

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

于 2015-07-27T13:18:23.753 回答
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
于 2015-07-17T14:00:17.863 回答