Python中truncate(0)后文件中的垃圾数据

11 投票
6 回答
8999 浏览
提问于 2025-04-17 10:43

假设有一个文件 test.txt,里面有一个字符串 'test'

现在,看看下面这段 Python 代码:

f = open('test', 'r+')
f.read()
f.truncate(0)
f.write('passed')
f.flush();

我本来希望 test.txt 现在里面是 'passed',但是里面却多了一些奇怪的符号!

更新:在截断后刷新并没有帮助。

6 个回答

3

截断操作并不会改变文件的位置。

另外要注意,即使文件是以读写模式打开的,你也不能随意在读和写之间切换(你需要先进行一个定位操作,才能从读切换到写,或者反过来)。

14

是的,确实 truncate() 这个函数不会改变位置,但说实话,这个用法简单得像死去一样:

f.read()
f.seek(0)
f.truncate(0)
f.close()

这个方法完全有效哦;)

7

这是因为 truncate 这个函数并不会改变文件的当前位置。

当你用 read() 读取文件时,文件的位置会移动到最后。所以接下来的 write 操作会从这个位置开始写入。不过,当你调用 flush() 时,它不仅会尝试把缓冲区的内容写入文件,还会进行一些错误检查,并修正当前的文件位置。如果在调用 truncate(0) 之后调用 flush(),由于缓冲区是空的,所以不会写入任何内容,然后它会检查文件的大小,并把位置放回到第一个合适的位置(也就是 0)。

更新

Python 的文件功能并不仅仅是 C 标准库函数的简单封装,但了解 C 函数有助于更准确地理解发生了什么。

根据 ftruncate 手册

调用 ftruncate() 不会修改寻址指针的值。

根据 fflush 手册

如果流指向的是输入流或更新流,并且最近的操作是输入,那么如果该流是可寻址的且不在文件末尾,则会刷新该流。刷新输入流会丢弃任何缓冲的输入,并调整文件指针,使得下一个输入操作可以访问到最后一个读取字节之后的字节。

这意味着如果你在 truncate 之前调用 flush,是没有效果的。我检查过,确实如此。

但是如果在 truncate 之后调用 flush

如果流指向的是输出流或更新流,并且最近的操作不是输入,fflush() 会导致该流的任何未写入数据被写入文件,并且底层文件的 st_ctime 和 st_mtime 字段会被标记为更新。

手册在解释输出流时没有提到寻址指针(这里我们的最后操作是 truncate)。

更新 2

我在 Python 源代码中发现了一些东西: Python-3.2.2\Modules\_io\fileio.c:837

#ifdef HAVE_FTRUNCATE
static PyObject *
fileio_truncate(fileio *self, PyObject *args)
{
    PyObject *posobj = NULL; /* the new size wanted by the user */
#ifndef MS_WINDOWS
    Py_off_t pos;
#endif

...

#ifdef MS_WINDOWS
    /* MS _chsize doesn't work if newsize doesn't fit in 32 bits,
       so don't even try using it. */
    {
        PyObject *oldposobj, *tempposobj;
        HANDLE hFile;

////// THIS LINE //////////////////////////////////////////////////////////////
        /* we save the file pointer position */
        oldposobj = portable_lseek(fd, NULL, 1);
        if (oldposobj == NULL) {
            Py_DECREF(posobj);
            return NULL;
        }

        /* we then move to the truncation position */
        ...

        /* Truncate.  Note that this may grow the file! */
        ...

////// AND THIS LINE //////////////////////////////////////////////////////////
        /* we restore the file pointer position in any case */
        tempposobj = portable_lseek(fd, oldposobj, 0);
        Py_DECREF(oldposobj);
        if (tempposobj == NULL) {
            Py_DECREF(posobj);
            return NULL;
        }
        Py_DECREF(tempposobj);
    }
#else

...

#endif /* HAVE_FTRUNCATE */

看看我指出的两行(///// This Line /////)。如果你的平台是 Windows,那么它会保存位置,并在 truncate 后返回这个位置。

让我惊讶的是,Python 3.2.2 中的大部分 flush 函数要么没有任何作用,要么根本没有调用 fflush 这个 C 函数。而 3.2.2 的 truncate 部分也没有详细的文档。不过,我在 Python 2.7.2 的源代码中发现了一些有趣的东西。首先,我在 Python-2.7.2\Objects\fileobject.c:812 中找到了 truncate 的实现:

 /* Get current file position.  If the file happens to be open for
 * update and the last operation was an input operation, C doesn't
 * define what the later fflush() will do, but we promise truncate()
 * won't change the current position (and fflush() *does* change it
 * then at least on Windows).  The easiest thing is to capture
 * current pos now and seek back to it at the end.
 */

所以总结一下,我认为这完全依赖于平台。我在默认的 Windows x64 上的 Python 3.2.2 中检查过,得到了和你一样的结果。不知道在 *nix 系统上会发生什么。

撰写回答