在Python中,生成器内应该使用with语句吗?需要明确的是,我不是在问是否可以使用装饰器从生成器函数创建上下文管理器。我的问题是,在生成器内使用with语句作为上下文管理器是否存在固有问题,因为它将至少在某些情况下捕获StopIteration和GeneratorExit异常。以下是两个示例。
其中Beazley的示例(第106页)提出了这个问题的一个很好的例子。我修改了它以使用with语句,以便在opener方法中的yield之后显式关闭文件。我还添加了两种可能在迭代结果时引发异常的方法。
当我为在生成器中使用的自定义上下文管理器进行定义时,我会收到运行时错误,提示我忽略了GeneratorExit的存在。例如:
其中Beazley的示例(第106页)提出了这个问题的一个很好的例子。我修改了它以使用with语句,以便在opener方法中的yield之后显式关闭文件。我还添加了两种可能在迭代结果时引发异常的方法。
import os
import fnmatch
def find_files(topdir, pattern):
for path, dirname, filelist in os.walk(topdir):
for name in filelist:
if fnmatch.fnmatch(name, pattern):
yield os.path.join(path,name)
def opener(filenames):
f = None
for name in filenames:
print "F before open: '%s'" % f
#f = open(name,'r')
with open(name,'r') as f:
print "Fname: %s, F#: %d" % (name, f.fileno())
yield f
print "F after yield: '%s'" % f
def cat(filelist):
for i,f in enumerate(filelist):
if i ==20:
# Cause and exception
f.write('foobar')
for line in f:
yield line
def grep(pattern,lines):
for line in lines:
if pattern in line:
yield line
pylogs = find_files("/var/log","*.log*")
files = opener(pylogs)
lines = cat(files)
pylines = grep("python", lines)
i = 0
for line in pylines:
i +=1
if i == 10:
raise RuntimeError("You're hosed!")
print 'Counted %d lines\n' % i
在这个例子中,上下文管理器成功地关闭了opener函数中的文件。当出现异常时,我能看到来自异常的跟踪信息,但生成器会默默停止。如果with语句捕获了异常,为什么生成器不继续呢?当我为在生成器中使用的自定义上下文管理器进行定义时,我会收到运行时错误,提示我忽略了GeneratorExit的存在。例如:
class CManager(object):
def __enter__(self):
print " __enter__"
return self
def __exit__(self, exctype, value, tb):
print " __exit__; excptype: '%s'; value: '%s'" % (exctype, value)
return True
def foo(n):
for i in xrange(n):
with CManager() as cman:
cman.val = i
yield cman
# Case1
for item in foo(10):
print 'Pass - val: %d' % item.val
# Case2
for item in foo(10):
print 'Fail - val: %d' % item.val
item.not_an_attribute
在第一种情况下,这个小演示很好地运行而没有引发任何异常,但是在第二种情况下失败了,其中引发了一个属性错误。我看到一个 RuntimeException
被提出,因为 with 语句已经捕获并忽略了一个 GeneratorExit
异常。
有人能帮忙澄清这个棘手的用例的规则吗?我怀疑是我在我的 __exit__
方法中做或没做某些事情。我尝试添加代码重新引发 GeneratorExit
,但没有帮助。