读取完整个文件是否会保持文件句柄处于打开状态?

399

如果你使用content = open('文件路径', 'r').read()读取一个整个文件,那么这个文件句柄会一直保持打开状态直到脚本结束吗?有没有更简洁的方法来读取整个文件呢?

4个回答

622
那个问题的答案在一定程度上取决于特定的Python实现。
要理解这一切,特别要注意实际的文件对象。在您的代码中,该对象仅在表达式中提到,并且在read()调用返回后立即变得无法访问。
这意味着该文件对象是垃圾。唯一剩下的问题是“垃圾收集器何时收集文件对象”。
在使用引用计数的CPython中,这种垃圾会立即被注意到,因此它将立即被收集。这通常不适用于其他Python实现。
更好的解决方案是使用以下模式,以确保文件已关闭:
with open('Path/to/file', 'r') as content_file:
    content = content_file.read()

使用 with 语句可以确保文件在块结束后立即关闭,即使发生异常也是如此。

编辑:更明确地说:

除了在 with 上下文管理器中“自动”调用的 file.__exit__() 之外,file.close() 自动调用的唯一其他方式(即除非您明确调用它自己)是通过 file.__del__()。这引导我们思考何时调用 __del__()

  

正确编写的程序不能假定终止程序之前最终器将在任何时候运行。

--https://devblogs.microsoft.com/oldnewthing/20100809-00/?p=13203

特别是:

  

对象从未被显式销毁;但是,当它们变得不可达时,它们可能会被垃圾收集。实现允许推迟垃圾收集或完全省略 - 如何实现垃圾收集是实现质量的问题,只要没有收集到仍然可达的对象即可。

  

[...]

  

CPython目前使用带有(可选)延迟检测循环链接垃圾的引用计数方案,该方案在大多数对象变得不可达时立即收集大多数对象,但不能保证收集包含循环引用的垃圾。

--https://docs.python.org/3.5/reference/datamodel.html#objects-values-and-types

(重点强调)

但正如它所建议的那样,其他实现可能会具有其他行为。例如,PyPy具有 6 种不同的垃圾回收实现!


25
有一段时间,几乎没有其他的Python实现; 但是依赖于实现细节并不真正符合Pythonic的理念。 - Karl Knechtel
它仍然是实现特定的,还是已经标准化了?在这种情况下不调用__exit__()听起来像是一个设计缺陷。 - rr-
2
@jgmjgm正是因为这三个问题,GC不可预测,try/finally很棘手,清理处理程序的高度常见无用性,才有了with的解决方案。 "显式关闭"和"使用with管理"之间的区别在于即使抛出异常也会调用退出处理程序。 您可以将close()放在finally子句中,但与使用with相比,这没有太大区别,有点混乱(3行额外代码而不是1行),并且有点难以完全正确地实现。 - SingleNegationElimination
1
我不理解的是为什么“with”会更可靠,因为它也不是显式的。是因为规范要求这样做,所以它总是被实现成这样吗? - jgmjgm
3
@jgmjgm,它更可靠是因为 with foo() as f: [...] 基本上等同于 f = foo(), f.__enter__(), [...] 和 f.__exit__()同时处理异常,因此 __exit__ 总是会被调用。因此文件总是会被关闭。 - neingeist
显示剩余3条评论

116
你可以使用 pathlib
对于 Python 3.5 及以上版本:
from pathlib import Path
contents = Path(file_path).read_text()

对于旧版本的Python,请使用pathlib2

$ pip install pathlib2

那么:

from pathlib2 import Path
contents = Path(file_path).read_text()

这是实际的read_text 实现代码:


def read_text(self, encoding=None, errors=None):
    """
    Open the file in text mode, read it, and close the file.
    """
    with self.open(mode='r', encoding=encoding, errors=errors) as f:
        return f.read()

我在这个解决方案中遇到了问题,也许有人能回答我的问题吗?提前感谢。 - Wolf

4

如果你需要逐行读取文件并处理每一行,可以使用以下方法:

with open('Path/to/file', 'r') as f:
    s = f.readline()
    while s:
        # do whatever you want to
        s = f.readline()

甚至更好的方法:

with open('Path/to/file') as f:
    for line in f:
        # do whatever you want to

2

将文件内容作为单个字符串检索不如将其存储为包含文件所有行的列表方便:

with open('Path/to/file', 'r') as content_file:
    content_list = content_file.read().strip().split("\n")

如下所示,需要在此主题的主要答案中添加连接方法.strip().split("\n")
在这里,.strip()仅删除整个文件字符串末尾的空格和换行符,并且.split("\n")通过在每个\n处拆分整个文件字符串来生成实际列表。
此外,这种方法可以将整个文件内容存储在变量中,这在某些情况下可能是需要的,而不是像在之前的答案中指出的逐行循环文件。

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