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

在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

在此示例中,上下文管理器成功关闭了打开器功能中的文件。当引发异常时,我看到了从异常追溯的轨迹,但是生成器无声地停止了。如果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__

如果提供了异常,并且该方法希望抑制该异常(即,防止其传播),则它应返回一个真值。否则,将在退出此方法后正常处理异常。

__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"

单行的'if'/'for'语句是否使用Python样式好? - python

我经常在这里看到某人的代码,看起来像是“单线”,这是一条单行语句,以传统的“if”语句或“for”循环的标准方式执行。我在Google周围搜索,无法真正找到可以执行的搜索类型?任何人都可以提出建议并最好举一些例子吗?例如,我可以一行执行此操作吗?example = "example" if "exam" in exam…

Python uuid4,如何限制唯一字符的长度 - python

在Python中,我正在使用uuid4()方法创建唯一的字符集。但是我找不到将其限制为10或8个字符的方法。有什么办法吗?uuid4()ffc69c1b-9d87-4c19-8dac-c09ca857e3fc谢谢。 参考方案 尝试:x = uuid4() str(x)[:8] 输出:"ffc69c1b" Is there a way to…

在返回'Response'(Python)中传递多个参数 - python

我在Angular工作,正在使用Http请求和响应。是否可以在“响应”中发送多个参数。角度文件:this.http.get("api/agent/applicationaware").subscribe((data:any)... python文件:def get(request): ... return Response(seriali…

无法注释掉涉及多行字符串的代码 - python

基本上,我很好奇这为什么会引发语法错误,以及如何用Python的方式来“注释掉”我未使用的代码部分,例如在调试会话期间。''' def foo(): '''does nothing''' ''' 参考方案 您可以使用三重双引号注释掉三重单引…

Python:BeautifulSoup-根据名称属性获取属性值 - python

我想根据属性名称打印属性值,例如<META NAME="City" content="Austin"> 我想做这样的事情soup = BeautifulSoup(f) //f is some HTML containing the above meta tag for meta_tag in soup(&#…