我最近写了一个方法,它返回一系列打开的文件;换句话说,类似于:

# this is very much simplified, of course
# the actual code returns file-like objects, not necessarily files
def _iterdir(self, *path):
    dr = os.path.join(*path)
    paths = imap(lambda fn: os.path.join(dr, fn), os.listdir(dr))

    return imap(open, paths)

从语法上讲,如果我执行如下操作,则不需要关闭生成的对象:
for f in _iterdir('/', 'usr'):
    make_unicorns_from(f)
    # ! f.close()

因此,我决定在上下文管理器中包装_iterdir
def iterdir(self, *path):
    it = self._iterdir(*path)

    while 1:
        with it.next() as f:
            yield f

这似乎工作正常。
我感兴趣的是这样做是否是一种良好的实践。我会在这个模式下遇到任何问题吗(如果抛出异常的话)?

最佳答案

我看到两个问题。一个是,如果一次尝试使用多个文件,则会出现以下情况:

list(iterdir('/', 'usr')) # Doesn't work; they're all closed.

第二种情况不太可能发生在cpython中,但是如果您有一个引用循环,或者您的代码曾经在不同的Python实现上运行,那么问题可能会显现出来。
如果在make_unicorns_from(f)中发生异常:
for f in iterdir('/', 'usr'):
    make_unicorns_from(f) # Uh oh, not enough biomass.

在垃圾收集生成器之前,不会关闭正在使用的文件。此时,将调用生成器的close方法,在最后一个GeneratorExit处引发一个yield异常,该异常将导致上下文管理器关闭文件。
对于cpython的引用计数,这通常是立即发生的。但是,在非引用计数的实现上或存在引用循环的情况下,在运行检测GC传递的循环之前,可能不会收集生成器。这可能需要一段时间。
我的直觉告诉我不要把文件关闭给来电者。你可以的
for f in _iterdir('/', 'usr'):
    with f:
        make_unicorns_from(f)

即使在生成器中没有with也会立即关闭它们,即使抛出异常也是如此。我不知道这是否比让生成器负责关闭文件更好。

关于python - 从上下文管理器中 yield 是一种好的做法吗?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/24691234/

10-12 22:48