【问题标题】:Cleanup in a generator when it goes out of scope超出范围时在生成器中进行清理
【发布时间】:2017-05-16 01:30:39
【问题描述】:

我有一个生成器,即使它从未迭代过,它也必须执行清理步骤:

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)
# ...

但是当生成器超出范围而没有任何人对其调用 next 时,当然它根本不会执行任何代码,因此 GeneratorExit 不会在其中引发,并且清理不会发生:

g = gen(data)
# g was never used before going out of scope
del g

即使生成器在有机会产生任何东西之前就超出范围,我如何重构代码以确保执行清理步骤?

【问题讨论】:

  • 你应该能够覆盖你的生成器上的__del__() 方法来处理这个问题。不是很漂亮,但是会在对象被销毁的时候调用。

标签: python python-3.x garbage-collection generator


【解决方案1】:

您可以为此使用上下文处理程序。这取决于您需要将生成器持久化多长时间。

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

【讨论】:

  • 生成器用于客户端代码,所以我无法控制它需要持续多长时间。所以不幸的是,我不能强加生成器只能通过上下文管理器使用的约束。
  • @max 我添加了一个可行的解决方案。如果您只是将生成器包装起来并强制它使用垃圾值迭代一次呢?
  • 是的,这似乎有效!你甚至可以不使用yield None:只需g = gen(data) 然后return itertools.chain([next(g)], g)。我仍然感觉到,生成器设计者并不认为清理从未运行过的生成器是一个好的用例——否则,他们会用更惯用的 API 来支持它。我想知道我是否错过了更好的方法。
  • @max Cleanup 通常使用try/finally 块(上下文管理器帮助包装)实现。即使发电机没有启动,也不能保证整个发电机会耗尽,从而在最后得到清理。
  • 更正:g = gen(data) 然后return itertools.chain([next(g)], g) 正如我所建议的那样会过早地获得输入;一般来说,这可能是不可接受的。
猜你喜欢
  • 2015-11-23
  • 2021-04-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-18
相关资源
最近更新 更多