Python 生成器超出范围时的清理
我有一个生成器,它必须执行清理步骤,即使从未迭代:Python 生成器超出范围时的清理,python,python-3.x,garbage-collection,generator,Python,Python 3.x,Garbage Collection,Generator,我有一个生成器,它必须执行清理步骤,即使从未迭代: def gen(data): while True: item = data.get() if item is None: break # ... try: yield transformed_item except GeneratorExit: break # clean up
def gen(data):
while True:
item = data.get()
if item is None:
break
# ...
try:
yield transformed_item
except GeneratorExit:
break
# clean up; must happen if gen was called
# ...
当我这样称呼它时,一切正常(即,清理发生):
for x in gen(data):
# ...
g = gen(data)
r = next(g)
# ...
或者像这样:
for x in gen(data):
# ...
g = gen(data)
r = next(g)
# ...
但是当生成器超出范围而没有任何人对其调用next
时,它当然不会执行任何代码,因此GeneratorExit
不会在其内部引发,也不会发生清理:
g = gen(data)
# g was never used before going out of scope
del g
我如何重构代码以保证清理步骤发生,即使生成器在有机会产生任何结果之前就超出了范围?您可以使用上下文处理程序来实现这一点。这取决于生成器需要保持多长时间
class Gen(object):
def __init__(self, data):
self.data = data
def __enter__(self):
return self._gen(self.data)
def __exit__(self, exc_type, exc_val, exc_tb):
# Cleanup
print 'Cleaning up'
def _gen(self, data):
for i in data:
yield i
然后它看起来像:
with Gen(data) as g:
r = next(g)
编辑:
考虑到您不能强制最终用户使用上下文管理器的限制,您是否可以将生成器的创建包装到另一个函数中并“种子”生成器
def gen(data):
g = _gen(data)
next(g)
return g
def _gen(data):
yield None
while True:
... # Rest of generator
您应该能够覆盖生成器上的
\uuu del\uuu()
方法来处理此问题。它并不漂亮,但当对象被销毁时会被调用。生成器用于客户端代码中,因此我无法控制它需要保留多长时间。因此,很遗憾,我不能强制规定生成器只能通过上下文管理器使用。@max我添加了一个可行的解决方案。如果你只是包装了生成器,并强迫它用一个垃圾值迭代一次呢?是的,这似乎有效!您甚至可以不使用生成None
:只需g=gen(data)
,然后返回itertools.chain([next(g)],g)
。但我还是感觉到,生成器设计者并不认为清理从未运行过的生成器是一个好的用例——否则,他们会用更惯用的API来支持它。我想知道我是否缺少一个更好的方法。@最大清理通常是通过try/finally
块(上下文管理器帮助包装)实现的。即使发电机没有启动,也不能保证整个发电机都会耗尽,从而在最后得到清理。更正:g=gen(data)
,然后返回itertools.chain([next(g)],g)
,正如我建议的那样,将过早地获得输入;一般来说,这可能是不可接受的。