在发电机中使用with语句是明智的吗?

时间:2015-03-13 19:33:08

标签: python generator with-statement contextmanager

考虑以下Python代码:

def values():
    with somecontext():
        yield 1
        yield 2
for v in values():
    print(v)
    break

在这种情况下,Python是否保证生成器正确关闭,从而退出上下文?

我意识到,在实践中,由于引用计数和生成器的急剧破坏,CPython将会出现这种情况,但Python是否保证了这种行为?我注意到它确实在Jython中不起作用,那么它应该被视为错误还是允许的行为?

2 个答案:

答案 0 :(得分:3)

是的,您可以在生成器中使用with语句而不会出现问题。 Python将正确处理上下文,因为在收集垃圾时将关闭生成器。

在生成器中,当生成器被垃圾收集时会引发GeneratorExit异常,因为它会在那时关闭:

>>> from contextlib import contextmanager
>>> @contextmanager
... def somecontext():
...     print 'Entering'
...     try:
...         yield None
...     finally:
...         print 'Exiting'
... 
>>> def values():
...     with somecontext():
...         yield 1
...         yield 2
... 
>>> next(values())
Entering
Exiting
1

这是PEP 342的一部分,关闭生成器会引发异常。收获没有引用的生成器应始终关闭该生成器,如果Jython没有关闭生成器,我认为这是一个错误。

参见规范摘要的第4和第5点:

  
      
  1. 为generator-iterators添加close()方法,该方法在生成器暂停时引发GeneratorExit。如果     然后,生成器引发StopIteration(通过正常退出,或     由于已经被关闭)或GeneratorExit(没有捕捉     例外),close()返回其调用者。如果发电机     产生一个值,引发RuntimeError。如果发电机     引发任何其他异常,它会传播给调用者。     如果生成器已经退出,close()什么都不做     例外或正常退出。

  2.   
  3. 添加支持以确保在生成器时调用close()      迭代器是垃圾收集的。

  4.   

唯一需要注意的是,在Jython,IronPython和PyPy中,垃圾收集器不能保证在退出解释器之前运行。如果这对您的应用程序很重要,您可以显式关闭生成器:

gen = values()
next(gen)
gen.close()

或明确触发垃圾收集。

答案 1 :(得分:0)

如果您的重点是安全性,您可以始终将生成器包装在contextlib.closing中 - 这似乎是最直接的解决方案:

from contextlib import closing

with closing(gen_values()) as values:
    for value in values:
        ...

事实上,如果是我,我会把这个功能写成

def gen_values():
    def inner():
        ...

    return closing(inner())

确保任何用户必须将其放入with才能使用它。