在python中,是否应该在生成器中使用with-statements?要清楚,我不是要求使用装饰器从生成器函数创建上下文管理器。我在询问是否存在使用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
这个小小的demo在case1中工作正常,没有异常引发,但在case2中失败,引发了属性错误。在这里,我看到RuntimeException
被引发,因为with语句已经捕获并忽略了GeneratorExit
异常。
有人可以帮助澄清这个棘手用例的规则吗?我怀疑这是我正在做的事情,或者不是我的__exit__
方法。我尝试添加代码来重新加注GeneratorExit
,但这没有帮助。
答案 0 :(得分:10)
object.__exit__
的
如果提供了异常,并且该方法希望抑制异常(即,防止它被传播),则它应该返回一个真值。否则,异常将在退出此方法时正常处理。
在__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"
答案 1 :(得分:1)
class CManager(object):
def __enter__(self):
print " __enter__"
return self
def __exit__(self, exctype, value, tb):
print " __exit__; excptype: '%s'; value: '%s'" % (exctype, value)
if exctype is None:
return
# only re-raise if it's *not* the exception that was
# passed to throw(), because __exit__() must not raise
# an exception unless __exit__() itself failed. But throw()
# has to raise the exception to signal propagation, so this
# fixes the impedance mismatch between the throw() protocol
# and the __exit__() protocol.
#
if sys.exc_info()[1] is not (value or exctype()):
raise