文件打开:这是不好的Python风格吗?

11

读取文件内容:

data = open(filename, "r").read()

打开的文件立即停止被任何地方引用,所以该文件对象最终会关闭...它不应该影响正在使用它的其他程序,因为该文件仅用于读取,而不是写入。

编辑:这实际上在我编写的一个项目中给我带来了麻烦-这促使我提出了问题。只有当你的内存耗尽时,文件对象才会被清除,而不是当你的文件句柄用尽时。因此,如果你这样做得太频繁,你可能会用尽文件描述符,并导致你试图打开文件的IO操作抛出异常。


3
请注意,这将把整个文件读入内存,无论它有多大。因此,请确保您能处理该文件。除此之外,我同意答案的观点。 - balpha
@balpha:但是答案是相互矛盾的。;)(我假设您在所有答案出现之前发表了评论。) - John Y
6个回答

30

只是为了记录: 这个方法略微长一点,但会立即关闭文件:

from __future__ import with_statement

with open(filename, "r") as f:
    data = f.read()

5
+1 我添加了 import ,以防他们使用 Python 2.5 :) - Andrew Hare
一个后续的问题:将 with open("t1.py", "r") as f: f.read() 写在同一行里真的很糟吗?我知道这样不够可读,但往往来说,读取文件是一种非常基本的操作,接下来的读者并不关心你是如何做到的。 - Jenn D.
@Jenn D:with 的一个作用是将所有处理限制在一个整洁的范围内。将 with open() as f: f.read() 放入一行中有点违背了 with 的作用。 - S.Lott
2
那根本就没有意义。无论是否有换行符,作用域都是相同的。我不喜欢它作为一种风格,但作用域并不涉及其中。 - Glenn Maynard
1
@Glenn:问题不在于换行符,而是整个缩进的代码块。with 的意图是在一个紧密绑定的上下文中读取和处理数据。使用一行 with this as that: variable = that.read() 的问题是,现在你从 with 块中获得了副作用,失去了整洁的绑定。 - S.Lott
我认为这是一个风格问题。我可以使用您的参数将“with”替换为“for”,而且我经常使用单行循环。 - Claudiu

7

确实,它最终会关闭,但“最终”可能不够快。特别是如果您在循环内使用它,系统可能会在GC处理文件对象之前耗尽文件句柄。


但是如果fileobj是引用计数的..它会直接变为0并立即被删除吗?在CPython(?)。 - u0b34a0f6ae
@kaizer.se:它并不一定会立即删除,只有当CPython需要更多内存时才会删除。 - Claudiu
等等,我错了。它应该立即被删除,因为这就是引用计数的工作原理。除非文件对象本身存在某些循环。这个答案是错误的吗?如果不是,那么为什么对象没有立即被删除? - Claudiu

4

虽然您的代码确实按照您所说的方式工作,但它仍然是不良风格。您的代码依赖于可能现在是正确的假设,但将来不一定是正确的。有可能在运行您的代码时,文件被打开而没有关闭会很重要。难道为了节省1或2行代码,冒这个风险真的值得吗?我认为不值得。


3

不,根据您的推理,我认为这是完全合理的Python风格。

更新:这里有很多关于文件对象是否立即清理的评论。与其猜测,我做了一些调查。这是我看到的:


从Python的object.h的注释中:

宏Py_INCREF(op)和 Py_DECREF(op)用于增加或 减少引用计数。当引用计数降至0时,Py_DECREF 调用对象的析构函数

查看Python的fileobject.c

文件对象的函数表指向函数file_dealloc。这个函数 调用close_the_file,然后关闭文件。


因此,在目前的CPython上,当没有更多的引用指向文件对象时,它会立即关闭,没有任何延迟。如果您认为这种解释是错误的,请发表评论说明您的观点。


合理,但操作系统资源可能不像您希望的那样完全可用。例如,在读取文件后立即尝试删除它可能无法正常工作,因为底层C库持有操作系统资源,即使Python“file”对象已被垃圾回收。在操作系统认为文件不再使用之前,您可能无法将其删除。 - S.Lott
@ S. Lott: 你是在说经过垃圾收集的“file”对象没有关闭(因此操作系统不知道它已不再使用)吗?我会期望删除文件对象会关闭文件,但我在文档中找不到任何信息。 - Eric O. Lebigot
5
我理解的是,CPython(大多数人在想到Python时会想到的参考实现)确实会在对象离开作用域时销毁所有未被引用的对象。在这种情况下,文件对象将在read()操作完成后立即离开作用域。所以它应该正好符合您的要求。然而,这并不是保证行为。其他Python实现(我认为Jython是一个典型的例子)可能会以不同的方式处理垃圾回收。如果您知道自己正在使用CPython并且不关心可移植性,则我的直觉是使用简单的实现。 - Jason R. Coombs
问题并不是垃圾收集本身。而是已经被释放的对象与任何操作系统资源之间的关系。我怀疑(但不确定)对象仅仅被释放而底层的操作系统文件句柄却没有被处理。 - S.Lott
@Jason,那是错误的。我的理解是垃圾回收不能保证一定会发生。 - Kenan Banks

2
尽管它能够按预期工作,但我认为它存在两个问题:
  1. 您的代码无法无缝扩展,因为您将整个文件读入内存中,这可能是必要或不必要的。
  2. 根据 Python 之禅(在 Python 提示符中尝试导入 import this),"显式优于隐式",通过未明确关闭文件,您可能会让维护您代码的人感到困惑。

明确是非常有帮助的!Python 鼓励明确的风格。

除此以外,在一个临时脚本中,您的风格是合理的。

也许您可以从这个答案中受益。


1

对我来说看起来很好。我经常读这样的文件。


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