f = open("apache.log", "r+")
print(f.tell())
f.read(1)
print(f.tell())
f.write("Love")
f.close()控制太输出的结果是0,1
文件操作后的结果是
Hello World!!!Love问题:代码读了一个字符,指针应该到第二个字符的位置,为什么写内容还是被追加到了文件的最后?
太长不看版结论:Python3之后的New I/O行为,实现了bufferd I/O ,导致了这个看似奇怪的行为
看到这个问题,首先试了试Python2.7 和Python3,发现Python3的open的行为和Python2确实是有所区别的。
f = open("apache.log", "r+")
print(f.tell())
f.read(1)
print(f.tell())
f.write("Love")
#
f.close()
在write后,f.tell()已经是原来的文件长+新写入的字符长了
这个问题在文件操作中,也就是APUE里的一个普通的seek问题,我们可以看看为什么在Python3、在哪里导致的这样的问题
io.open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
This is an alias for the builtin open() function.
而不再是Python2里的内建函数了
open返回的是:
In [2]: f
Out[2]: <_io.textiowrapper name="test.txt" mode="r+" encoding="UTF-8">
下划线开头的这个_io是一个c模块:
In [4]: _io?
Type: module
String form:
Docstring:
The io module provides the Python interfaces to stream handling. The
builtin open function is defined in this module.
它是用来提供流处理的接口的,继承关系什么的在上面的链接里有说。
看了下Python3.5的代码,TextIOWrapper的write实现在textio.c:_io_TextIOWrapper_write_impl这个函数里
看了一下PEP 3116 -- New I/O,和里面的代码,确定导致这个问题的逻辑核心是:
if (self->pending_bytes_count > self->chunk_size || needflush ||
text_needflush) {
if (_textiowrapper_writeflush(self) < 0)
return NULL;
}
if (needflush) {
ret = PyObject_CallMethodObjArgs(self->buffer, _PyIO_str_flush, NULL);
if (ret == NULL)
return NULL;
Py_DECREF(ret);
}
这篇PEP设计了一个三层的Python3 IO实现:raw层、Buffer层、Text层,我们通常直接用的Text层。告诉我们,打开一个流对象后(例如Open),这个对象会有一个buffer属性。作为缓冲层对应的对象。
上面贴的代码描述了当write调用时,实际上调用的是self->buffer的写相关的方法。
我们可以实验一下:
$ cat test.txt
hello world
# zhutou @ Phoenix in ~ [1:19:01]
$ cat test.txt|wc
1 2 12
In [1]: f = open("test.txt","r+")
Out[1]: <_io.textiowrapper name="test.txt" mode="r+" encoding="UTF-8">
In [2]: f.read(1)
Out[2]: 'h'
In [3]: f.buffer
Out[3]: <_io.bufferedrandom name="test.txt">
In [4]: f.buffer.tell()
Out[4]: 12
可以看到,虽然我们只读了一个字符,但实际上这个文件对应的buffer对象,指针已经指到了这个文件的末尾,所以当调用write方法时,对这个buffer进行写也就写到了文件末尾。在常见的操作系统上,这个buffer预取的长度是8k,以减少磁盘的随机读取,增加磁盘的顺序读取
_textiowrapper_writeflush的实现核心如下,可以清晰地看到这个对应的逻辑
static int
_textiowrapper_writeflush(textio *self)
{
PyObject *pending, *b, *ret;
pending = self->pending_bytes;
b = _PyBytes_Join(_PyIO_empty_bytes, pending);
do {
ret = PyObject_CallMethodObjArgs(self->buffer,
_PyIO_str_write, b, NULL);
} while (ret == NULL && _PyIO_trap_eintr());
return 0;
}
这一点在Python中可以得到验证:
In [5]: f.buffer.seek(1)
Out[5]: 1
In [6]: f.write("Love")
Out[6]: 4
In [7]: f.close()
现在符合直觉了,文件内容已经是hLove world 了~
由此得到了开头的结论:Python3和Python2的文件行为不一致是文件buffer造成的,buffered IO本身是为了解决读取文件的效率问题。(PEP中说是为了和Java看齐。。俺不懂什么java,不知道java会不会有这个问题。。The new I/O spec is intended to be similar to the Java I/O libraries, but generally less confusing
然后还更加优越2333
在io.TextIOWrapper类中,我们可以构造时指定write_through=True 避免写的时候经过buffer
但我找了一下open函数的文档和对应的实现,没有找到改这个参数的地方~