Python 3.x Python3开放缓冲参数看起来很奇怪

Python 3.x Python3开放缓冲参数看起来很奇怪,python-3.x,io,python-3.7,Python 3.x,Io,Python 3.7,从 缓冲是用于设置缓冲策略的可选整数。 传递0以关闭缓冲(仅在二进制模式下允许),传递1至 选择行缓冲(仅在文本模式下可用),然后选择一个大于1的整数 以字节为单位指示固定大小区块缓冲区的大小。当没有 如果给定缓冲参数,则默认缓冲策略的作用如下 如下: 二进制文件缓冲在固定大小的块中;缓冲区的大小是使用一种启发式方法来选择的,该方法试图确定底层缓冲区的大小 设备的“块大小”和返回io.DEFAULT\U BUFFER\U大小。在…上 在许多系统中,缓冲区通常为4096或8192字节长。 “交互式

缓冲是用于设置缓冲策略的可选整数。 传递0以关闭缓冲(仅在二进制模式下允许),传递1至 选择行缓冲(仅在文本模式下可用),然后选择一个大于1的整数 以字节为单位指示固定大小区块缓冲区的大小。当没有 如果给定缓冲参数,则默认缓冲策略的作用如下 如下:

二进制文件缓冲在固定大小的块中;缓冲区的大小是使用一种启发式方法来选择的,该方法试图确定底层缓冲区的大小 设备的“块大小”和返回io.DEFAULT\U BUFFER\U大小。在…上 在许多系统中,缓冲区通常为4096或8192字节长。 “交互式”文本文件(isatty()返回True的文件)使用行缓冲。其他文本文件使用上述策略 对于二进制文件

我用文本模式打开一个名为
test.log
的文件,并将缓冲设置为16。所以我认为区块大小是16,当我将32字节的字符串写入文件时。它将调用
write
(syscall)两次。但实际上,它只调用一次

使用
strace-e write python3 test.py
跟踪系统调用,并获得以下信息

write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 320) = 320

缓冲是什么意思?

这个答案适用于CPython 3.7,其他Python实现可能有所不同

文本模式下的
open()
函数返回。
\u io.TextIOWrapper()
调用了大小为8192字节的内部“buffer”(它是),对于文本模式
w
它还具有
\u io.BufferedWriter()
的句柄,对于文本模式
a
它具有
\u io.BufferedRandom()
的句柄。
\u io.BufferedWriter()
/
\u io.BufferedRandom()
的大小由
open()
函数中的参数
buffering
指定

当您调用到
\u io.TextIOWrapper().write(“一些文本”)
时,它会将文本添加到内部
挂起的\u字节
缓冲区中。在一些写入之后,您将填充
挂起的\u字节
缓冲区,然后它将被写入
\u io.BufferedWriter()
内的缓冲区。当您同时填充
\u io.BufferedWriter()
中的缓冲区时,它将被写入目标文件

当您以二进制模式打开文件时,您将直接从
缓冲
参数中获得使用缓冲区大小初始化的
io.BufferedWriter()
/
\io.BufferedRandom()
对象

让我们看一些例子。我将从使用二进制模式的更简单的一个开始

# Case 1
with open('test.log', 'wb', buffering=16) as f:
    for _ in range(5):
        f.write(b'a'*15)
strace输出:

write(3, "aaaaaaaaaaaaaaa", 15)         = 15
write(3, "aaaaaaaaaaaaaaa", 15)         = 15
write(3, "aaaaaaaaaaaaaaa", 15)         = 15
write(3, "aaaaaaaaaaaaaaa", 15)         = 15
write(3, "aaaaaaaaaaaaaaa", 15)         = 15
write(3, "aaaaaaaaaaaaaaaaa", 17)       = 17
write(3, "aaaaaaaaaaaaaaaaa", 17)       = 17
write(3, "aaaaaaaaaaaaaaaaa", 17)       = 17
write(3, "aaaaaaaaaaaaaaaaa", 17)       = 17
write(3, "aaaaaaaaaaaaaaaaa", 17)       = 17
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 16384) = 16384
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 16384) = 16384
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 8192) = 8192
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 16384) = 16384
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 16384) = 16384
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 24576) = 24576
在第一次迭代中,它用15个字节填充缓冲区。在第二次迭代中,它发现再添加15个字节会使缓冲区溢出,因此它首先刷新缓冲区(调用system
write
),然后保存这些新的15个字节。在下一次迭代中,同样的情况再次发生。在缓冲区中的最后一次迭代之后是15b,在文件结束时写入(将
保留在
上下文中)

第二种情况,我将尝试向缓冲区写入比缓冲区大小更多的数据:

# Case 2
with open('test.log', 'wb', buffering=16) as f:
    for _ in range(5):
        f.write(b'a'*17) 
strace输出:

write(3, "aaaaaaaaaaaaaaa", 15)         = 15
write(3, "aaaaaaaaaaaaaaa", 15)         = 15
write(3, "aaaaaaaaaaaaaaa", 15)         = 15
write(3, "aaaaaaaaaaaaaaa", 15)         = 15
write(3, "aaaaaaaaaaaaaaa", 15)         = 15
write(3, "aaaaaaaaaaaaaaaaa", 17)       = 17
write(3, "aaaaaaaaaaaaaaaaa", 17)       = 17
write(3, "aaaaaaaaaaaaaaaaa", 17)       = 17
write(3, "aaaaaaaaaaaaaaaaa", 17)       = 17
write(3, "aaaaaaaaaaaaaaaaa", 17)       = 17
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 16384) = 16384
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 16384) = 16384
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 8192) = 8192
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 16384) = 16384
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 16384) = 16384
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 24576) = 24576
这里发生的情况是,在第一次迭代中,它将尝试写入缓冲区17b,但它无法适应缓冲区17b,因此直接写入文件,缓冲区保持为空。这适用于每个迭代

现在让我们看看文本模式

# Case 3
with open('test.log', 'w', buffering=16) as f:
    for _ in range(5):
        f.write('a'*8192)
strace输出:

write(3, "aaaaaaaaaaaaaaa", 15)         = 15
write(3, "aaaaaaaaaaaaaaa", 15)         = 15
write(3, "aaaaaaaaaaaaaaa", 15)         = 15
write(3, "aaaaaaaaaaaaaaa", 15)         = 15
write(3, "aaaaaaaaaaaaaaa", 15)         = 15
write(3, "aaaaaaaaaaaaaaaaa", 17)       = 17
write(3, "aaaaaaaaaaaaaaaaa", 17)       = 17
write(3, "aaaaaaaaaaaaaaaaa", 17)       = 17
write(3, "aaaaaaaaaaaaaaaaa", 17)       = 17
write(3, "aaaaaaaaaaaaaaaaa", 17)       = 17
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 16384) = 16384
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 16384) = 16384
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 8192) = 8192
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 16384) = 16384
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 16384) = 16384
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 24576) = 24576
首先回想一下,
pending_bytes
的大小为8192b。在第一次迭代中,它将8192字节(从代码:
'a'*8192
)写入
pending_bytes
缓冲区。在第二次迭代中,它将另外8192个字节添加到
pending_buffer
中,发现它大于8192(大小为
pending_buffer
buffer),并将其写入底层
\u io.BufferedWriter()
\u io.BufferedWriter()
中的缓冲区大小为16 B(
缓冲
参数),因此它将立即写入文件(与案例2相同)。现在,
pending_buffer
是空的,在第三次迭代中,它再次被8192b填充。在第四次迭代中,它添加了另一个8192b
pending_字节
buffer溢出,并再次像在第二次迭代中一样直接写入文件。在上一次迭代中,它将8192b添加到
挂起字节
缓冲区中,当文件关闭时,缓冲区将被刷新

最后一个示例包含大于8192B的缓冲。为了更好地解释,我又添加了2次迭代

# Case 4
with open('test.log', 'w', buffering=30000) as f:
    for _ in range(7):
        f.write('a'*8192)
strace输出:

write(3, "aaaaaaaaaaaaaaa", 15)         = 15
write(3, "aaaaaaaaaaaaaaa", 15)         = 15
write(3, "aaaaaaaaaaaaaaa", 15)         = 15
write(3, "aaaaaaaaaaaaaaa", 15)         = 15
write(3, "aaaaaaaaaaaaaaa", 15)         = 15
write(3, "aaaaaaaaaaaaaaaaa", 17)       = 17
write(3, "aaaaaaaaaaaaaaaaa", 17)       = 17
write(3, "aaaaaaaaaaaaaaaaa", 17)       = 17
write(3, "aaaaaaaaaaaaaaaaa", 17)       = 17
write(3, "aaaaaaaaaaaaaaaaa", 17)       = 17
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 16384) = 16384
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 16384) = 16384
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 8192) = 8192
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 16384) = 16384
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 16384) = 16384
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 24576) = 24576
迭代:

  • 将8192 B添加到
    挂起的\u字节中
  • 将8192 B添加到
    pending_字节
    中,但它超过了最大大小,因此它被写入基础
    \u io.BufferedWriter()
    中并保持在那里(
    pending_字节
    现在为空)
  • 将8192 B添加到
    挂起的\u字节中
  • 将8192 B添加到
    挂起的\u字节中
    ,但它超过了最大大小,因此它尝试写入基础
    io.BufferedWriter()
    。但它将超过底层缓冲区的最大容量,原因是
    16384+16384>30000
    (从迭代2开始,第一个16384b仍然存在),因此它首先将旧的16384b写入文件,然后将那些新的16384b(从
    挂起的字节
    )放入缓冲区。(现在,
    pending_字节
    缓冲区再次为空)
  • 与3相同
  • 与4相同
  • 当前
    pending_buffer
    是空的,
    \u io.bufferedWriter()
    包含16384 B。在这个迭代中,它用8192 B填充
    pending_buffer
    。就是这样
  • 当程序带部分离开
    时,它关闭文件。关闭过程如下:

  • 将8192 B从
    挂起缓冲区写入
    io.BufferedWriter()
    (可能原因
    8192+16384<30000
  • 将(
    8192+16384
    =)24576 B写入文件
  • 关闭文件描述符
  • 顺便说一句,目前我不知道为什么会有