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)
,正如我建议的那样,将过早地获得输入;一般来说,这可能是不可接受的。