Python生成器:当调用生成器的for循环突然返回时,它如何关闭文件句柄?

8
如果函数lookForSpecificLine返回True(也就是说,如果文件“foo.txt”包含了targetLine),Python如何知道关闭文件句柄?文件“foo.txt”会保持打开状态吗?
def lines(filename):
    with open(filename, encoding='utf-8') as file:
        for line in file:
            yield line

def lookForSpecificLine(targetLine):
    for line in lines('foo.txt'):
        if targetLine == line:
            return True
    return False
3个回答

14
只要生成器对象存活,文件将保持打开状态。当生成器被垃圾回收时(通常在lookForSpecificLine函数结束时),Python会调用它的close方法,作为在PEP 342中描述的协程协议的一部分。close方法会导致Python在生成器的代码中抛出一个GeneratorExit异常,在它暂停的位置(正好在yield语句之后)。由于您没有捕获该异常(通常不应该),它会退出循环,并导致with语句关闭文件。
请注意,如果lookForSpecificLine更复杂,并且存在某些风险导致它引发一个异常(该异常将在较高级别捕获),那么情况可能不会快速清除。因为异常跟踪将保持函数的堆栈帧处于活动状态,所以生成器不会立即被垃圾回收,文件也不会关闭。

1
这是因为文件对象也是上下文管理器。基本上,一个类需要定义一个__enter__和一个__exit__函数,分别在with块的开头和结尾调用。
以下是一个简单的上下文管理器示例,在with块的开头打印"on enter",在结尾处打印"on exit":
class contextmanager:
    def __enter__(self):
        print('on enter')
    def __exit__(self, type, value, traceback):
        print('on exit')

并附一个使用它的示例:

>>> with contextmanager():
...     print('inside with')
...
on enter
inside with
on exit

现在让我们尝试在with语句中引发异常:
>>> with contextmanager():
...     raise Exception()
...
on enter
on exit
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
Exception

你可以看到,__exit__ 函数内的代码会在 with 语句结束时被调用,无论其是否正常执行或引发异常。对于文件对象来说,它们使用这个函数来关闭文件句柄和清理工作。

当for循环提前返回True时,如何让for循环告诉生成器内部的文件对象调用其__exit__函数? - platypus
with语句相当于一个带有__exit__函数的try/finally语句块,该函数在finally语句块内被调用。 - krock

0

在离开with块后,文件将被关闭。有关with语句的更多信息,请参见PEP343this guide


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接