在 python 中,应该在生成器中使用 with 语句吗?需要明确的是,我不是在询问使用装饰器从生成器函数创建上下文管理器。我在问是否存在使用 with 语句作为生成器内部的上下文管理器的固有问题,因为它至少在某些情况下会捕获StopIteration
异常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
这个小演示在 case1 中运行良好,没有引发异常,但在引发属性错误的 case2 中失败。在这里,我看到了一个RuntimeException
提升,因为 with 语句已捕获并忽略了一个GeneratorExit
异常。
有人可以帮助澄清这个棘手用例的规则吗?我怀疑这是我正在做的事情,或者没有按照我的__exit__
方法做。我尝试添加代码来 re-raise GeneratorExit
,但这没有帮助。