python - 如何在生成器中使用 python 上下文管理器

标签 python generator with-statement

在 python 中,应该在生成器中使用 with 语句吗?需要明确的是,我不是在询问使用装饰器从生成器函数创建上下文管理器。我在问是否存在使用 with 语句作为生成器内的上下文管理器的固有问题,因为它至少在某些情况下会捕获 StopIterationGeneratorExit 异常。下面是两个例子。

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/

相关文章:

python - 为什么我的感知器不能完美地分离数量少于特征数量的点?

python - "max_q_size"中使用的参数 "model.fit_generator"是什么?

C# "Generator"方法

python - 如何生成一组四个特定字符?

python的 `with`语句目标竟然是None

python - 比较 2 组 3D 浊点

python - 如何减小绘图的大小,保持数字轴相同?

python - 这段代码中是如何调用readline()方法的?

oracle - 选择获取空值行以进行串联

sql - 在 Sqlite 的以下查询中重用 SQL 查询的结果