【问题标题】:Python file objects, closing, and destructorsPython 文件对象、关闭和析构函数
【发布时间】:2013-04-01 19:14:28
【问题描述】:

tempfile.NamedTemporaryFile()的描述说:

如果 delete 为真(默认值),则文件一经删除即被删除 已关闭。

在某些情况下,这意味着文件在 Python 解释器结束。例如,在运行以下测试时 py.test,临时文件还在:

from __future__ import division, print_function, absolute_import
import tempfile
import unittest2 as unittest
class cache_tests(unittest.TestCase):
    def setUp(self):
        self.dbfile = tempfile.NamedTemporaryFile()
    def test_get(self):
        self.assertEqual('foo', 'foo')

在某种程度上这是有道理的,因为这个程序从来没有明确地 关闭文件对象。对象关闭的唯一其他方式 大概在__del__ 析构函数中,但这里的语言 引用声明“不保证__del__() 方法是 调用解释器退出时仍然存在的对象。" 所以 到目前为止,一切都与文档一致。

但是,我对此的含义感到困惑。如果不是 保证文件对象在解释器退出时关闭,可以吗 可能发生某些数据已成功写入 即使程序正常退出,(缓冲的)文件对象也会丢失, 因为它仍然在文件对象的缓冲区中,并且文件对象 从来没有关闭过?

不知何故,这对我来说似乎不太可能和非 Pythonic,以及 open() 文档也不包含任何此类警告。所以我 (暂时)得出结论,文件对象毕竟是保证 关闭。

但是这个魔法是怎么发生的,为什么NamedTemporaryFile()不能用 确保文件被删除的相同魔法?

编辑: 请注意,我在这里不是在谈论文件 descriptors(由操作系统缓冲并在程序退出时由操作系统关闭),而是关于 Python 文件可以实现自己的缓冲的对象。

【问题讨论】:

  • 这篇文章似乎包含很多假设和论据,而实际问题很少。

标签: python file-io destructor


【解决方案1】:

在 Windows 上,NamedTemporaryFile 使用特定于 Windows 的扩展名 (os.O_TEMPORARY) 来确保文件在关闭时被删除。如果进程以任何方式被终止,这也可能有效。但是在 POSIX 上没有明显的等价物,很可能是因为在 POSIX 上您可以简单地删除仍在使用的文件;它只删除名称,并且文件的内容仅在关闭后才被删除(以任何方式)。但确实假设我们希望文件名一直持续到文件关闭,就像 NamedTemporaryFile 一样,那么我们需要“魔法”。

我们不能使用与刷新缓冲文件相同的魔法。发生的情况是 C 库处理它(在 Python 2 中):文件是 C 中的 FILE 对象,并且 C 保证它们在正常程序退出时被刷新(但如果进程被终止则不会)。在 Python 3 的情况下,有自定义 C 代码可以实现相同的效果。但它特定于这个用例,而不是任何可直接重用的东西。

这就是 NamedTemporaryFile 使用自定义 __del__ 的原因。事实上,__del__ 不能保证在解释器退出时被调用。 (我们可以通过引用 NamedTemporaryFile 实例的全局引用循环来证明这一点;或者运行 PyPy 而不是 CPython。)

附带说明,NamedTemporaryFile 可以更稳健地实现,例如通过向atexit 注册自身以确保随后删除文件名。但是你也可以自己调用它:如果你的进程不使用无限数量的 NamedTemporaryFiles,它只是atexit.register(my_named_temporary_file.close)

【讨论】:

  • 对于 Python 3 的情况:在 3.0 中,io.BufferedWriter 以纯 Python 实现,而在 Python 3.1 及更高版本中,纯 Python 实现仍可用作 _pyio。这些模块如何使用“自定义 C 代码”?
  • 我不详细了解 Python 3,但我知道 _io 模块至少在当前的 Python 3.x 中是用 C 编写的(Python 3.0 现在已经过时了)。如果曾经有一个纯 Python 版本,它曾经或可能正在使用atexit;如果它使用__del__,那么它会遇到与这个问题相同的问题。
  • @ArminRigo:你说的是io,它是一个C 模块。 _pyioio 的功能等效的慢速纯 Python 版本,也存在于 Python 3.3 中。它也不使用atexit。唔。所以也许_pyio 缓冲区实际上不会丢失......
  • 好吧,我刚试过,Python 3.3总是丢失数据,不管是使用_pyio还是内置的_io。运行bpaste.net/show/94517 最终会导致文件'foo' 为空,因为自引用会阻止其常规__del__ 在解释器退出时迅速运行。据我所知,这是一个令人讨厌的错误,我会报告它。
  • @ArminRigo:你有错误号还是测试脚本?您的 bpaste 链接现在是 404..
【解决方案2】:

在任何版本的 *nix 上,所有文件描述符都会在进程完成时关闭,这由操作系统负责。 Windows 在这方面可能完全相同。如果不深入研究源代码,我不能 100% 权威地说实际发生了什么,但很可能发生的是:

  • 如果deleteFalse,则调用unlink()(或其他操作系统上的类似函数)。这意味着当进程退出并且没有更多打开的文件描述符时,文件将被自动删除。在进程运行时,该文件仍将保留。

  • 如果deleteTrue,则可能使用了C 函数remove()。这将在进程退出之前强制删除文件。

【讨论】:

  • 您在这里非常接近正确。处理匿名临时文件的正常方法是打开文件,立即取消链接,然后继续使用文件描述符。普通的 Unix 文件系统仅在链接计数为零并且不保持打开的文件描述符之后才回收 inode。
  • @JimDennis 是的,谢谢,这是对所发生情况的更准确描述。
  • @Yuushi:恐怕这在几个方面是错误的。 1) 如果delete 为false,则文件不会自动删除,而是在程序退出后保留。 2) C remove 函数只调用unlink。没有这样的事情是“强制删除”。 3) 我说的是 Python 文件 objects,而不是文件 descriptors。虽然文件对象通常具有底层描述符,但我担心在将数据写入描述符之前文件对象内部会发生缓冲。
【解决方案3】:

文件缓冲由操作系统处理。如果您打开文件后不关闭它,那是因为您假设操作系统将在所有者存在后刷新缓冲区并关闭文件。这不是 Python 魔术,这是你的操作系统在做的事情。 __del__() 方法与 Python 相关,需要显式调用。

【讨论】:

猜你喜欢
  • 2021-09-23
  • 1970-01-01
  • 2010-10-08
  • 2014-09-10
  • 2016-10-28
  • 2018-03-02
  • 2016-02-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多