python分配buffer_Python3 Buffered I/O浅析

本文探讨了Python3中打开文件时出现的缓冲I/O行为,解释了为何在读取后写入仍会被追加到文件末尾。这主要归因于Python3的Buffered I/O实现。通过分析Python3的源代码和PEP 3116,展示了缓冲层如何影响文件操作,以及如何通过`write_through=True`避免写操作经过缓冲层,但该选项在标准`open()`函数中无法直接设置。
摘要由CSDN通过智能技术生成

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函数的文档和对应的实现,没有找到改这个参数的地方~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值