在 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__
方法中没有做。我尝试添加代码以重新引发 GeneratorExit
,但这没有帮助。
最佳答案
来自 Data model entry for object.__exit__
If an exception is supplied, and the method wishes to suppress the exception (i.e., prevent it from being propagated), it should return a true value. Otherwise, the exception will be processed normally upon exit from this method.
在您的 __exit__
函数中,您将返回 True
这将抑制 所有 异常。如果您将其更改为返回 False
,异常将继续照常引发(唯一的区别是您保证您的 __exit__
函数被调用并且您可以一定要自己清理)
比如把代码改成:
def __exit__(self, exctype, value, tb):
print " __exit__; excptype: '%s'; value: '%s'" % (exctype, value)
if exctype is GeneratorExit:
return False
return True
允许您做正确的事情,而不是抑制 GeneratorExit
。现在您只看到属性错误。也许经验法则应该与任何异常处理相同——只有在你知道如何处理异常的情况下才拦截它们。有一个 __exit__
返回 True
比有一个裸的除外:
try:
something()
except: #Uh-Oh
pass
请注意,当 AttributeError
被引发(而不是被捕获)时,我相信这会导致生成器对象上的引用计数下降到 0,然后触发 GeneratorExit
生成器中的异常,以便它可以自行清理。使用我的 __exit__
,尝试以下两种情况,希望你能明白我的意思:
try:
for item in foo(10):
print 'Fail - val: %d' % item.val
item.not_an_attribute
except AttributeError:
pass
print "Here" #No reference to the generator left.
#Should see __exit__ before "Here"
和
g = foo(10)
try:
for item in g:
print 'Fail - val: %d' % item.val
item.not_an_attribute
except AttributeError:
pass
print "Here"
b = g #keep a reference to prevent the reference counter from cleaning this up.
#Now we see __exit__ *after* "Here"
关于python - 如何在生成器中使用 python 上下文管理器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15353220/