Python中truncate(0)后文件中的垃圾数据
假设有一个文件 test.txt
,里面有一个字符串 'test'
。
现在,看看下面这段 Python 代码:
f = open('test', 'r+')
f.read()
f.truncate(0)
f.write('passed')
f.flush();
我本来希望 test.txt
现在里面是 'passed'
,但是里面却多了一些奇怪的符号!
更新:在截断后刷新并没有帮助。
6 个回答
截断操作并不会改变文件的位置。
另外要注意,即使文件是以读写模式打开的,你也不能随意在读和写之间切换(你需要先进行一个定位操作,才能从读切换到写,或者反过来)。
是的,确实 truncate()
这个函数不会改变位置,但说实话,这个用法简单得像死去一样:
f.read()
f.seek(0)
f.truncate(0)
f.close()
这个方法完全有效哦;)
这是因为 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 系统上会发生什么。