我正在编写一个看起来像“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立即清理它们并在生成器被垃圾收集时关闭它们?
谢谢,
伊恩
答案 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 protocol和iterator 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