考虑以下Python代码:
def values():
with somecontext():
yield 1
yield 2
for v in values():
print(v)
break
在这种情况下,Python是否保证生成器正确关闭,从而退出上下文?
我意识到,在实践中,由于引用计数和生成器的急剧破坏,CPython将会出现这种情况,但Python是否保证了这种行为?我注意到它确实在Jython中不起作用,那么它应该被视为错误还是允许的行为?
答案 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点:
为generator-iterators添加
close()
方法,该方法在生成器暂停时引发GeneratorExit
。如果 然后,生成器引发StopIteration
(通过正常退出,或 由于已经被关闭)或GeneratorExit
(没有捕捉 例外),close()
返回其调用者。如果发电机 产生一个值,引发RuntimeError
。如果发电机 引发任何其他异常,它会传播给调用者。 如果生成器已经退出,close()
什么都不做 例外或正常退出。- 醇>
添加支持以确保在生成器时调用
close()
迭代器是垃圾收集的。
唯一需要注意的是,在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
才能使用它。