在生成器超出范围时清理生成器的优雅机制?

Elegant mechanism to clean up a generator as it goes out of scope?

我在堆队列中使用多个生成器来循环访问磁盘上已排序的文件。通常 heapq 在超出范围之前不会完全耗尽,因此底层生成器永远不会达到 StopIteration 条件。

我希望能够将处理程序附加到生成器或其他一些优雅的机制,以便在生成器超出范围时删除磁盘上的文件。这些文件本身是临时的,因此可以删除它们。但是,如果不删除它们,程序最终将用临时文件填满磁盘。以下是生成器供参考:

def _read_score_index_from_disk(file_name, buffer_size=8*10000):
    """Generator to yield a float/int value from a file, does buffering
    and file managment to avoid keeping file open while function is not
    invoked"""

    file_buffer = ''
    file_offset = 0
    buffer_offset = 1

    while True:
        if buffer_offset > len(file_buffer):
            data_file = open(file_name, 'rb')
            data_file.seek(file_offset)
            file_buffer = data_file.read(buffer_size)
            data_file.close()
            file_offset += buffer_size
            buffer_offset = 0
        packed_score = file_buffer[buffer_offset:buffer_offset+8]
        buffer_offset += 8
        if not packed_score:
            break
        yield struct.unpack('fi', packed_score)

我知道 atexit 处理程序,但它在我的情况下不起作用,因为此代码将用于较长的 运行 过程。

当生成器超出范围并被删除时,它们 generator.close() method is called, which in turn raises a GeneratorExit exception 在您的生成器函数中。

简单处理那个异常:

def _read_score_index_from_disk(file_name, buffer_size=8*10000):
    # ...

    try:
        # generator loop
    except GeneratorExit:
        # clean up after the generator

如果你使用 finally: 而不是 except GeneratorExit: 那么当生成器自然结束时(因为你没有来处理“GeneratorExit”。

您可以从函数中创建上下文管理器来处理任何清理任务。

这里有一个简单的例子来说明我的意思:

from contextlib import contextmanager

def my_generator():
    for i in range(10):
        if i > 5:
            break
        yield i

@contextmanager
def generator_context():
    yield my_generator()
    print("cleaning up")

with generator_context() as generator:
    for value in generator:
        print(value)

输出:

0
1
2
3
4
5
cleaning up