在“ r +”中,为什么在读取一行之后*写文本文件会使其在末尾而不是在f.tell()位置写?

具有这样的文本文件:

line one
line two
line three

并运行以下代码:

with open('file','r+') as f:
    print(f.tell())
    print(f.readline().strip())
    print(f.tell())
    # f.seek(f.tell())
    f.write('Hello')
    print(f.tell())

结果是在文件的末尾写上“ Hello”一词:

line one
line two
line threeHello

我认为写作部分将从最近读取的字符位置开始(在line one之后),但是除非我取消注释f.seek(f.tell()),否则不要这样做。我可能缺少一些基本知识,但我在Python文档中找不到任何能深入解释其工作原理的内容。这是怎么回事,是什么使它在那写了字?如果我不先阅读而是开始写作,为什么不发生这种情况?

f.tell()的打印值如下:

0
9
39
h213haze 回答:在“ r +”中,为什么在读取一行之后*写文本文件会使其在末尾而不是在f.tell()位置写?

这似乎是io.TextIOWrapper(在文本模式下open返回的类)与io.BufferedRandom(在+模式下包装的类)如何交互的错误。 )。

如果您将测试用例更改为以二进制模式运行:

with open('file','rb+') as f:
    print(f.tell())
    print(f.readline().strip())
    print(f.tell())
    # f.seek(f.tell())
    f.write(b'Hello')
    print(f.tell())

无论是否包含多余的f.seek(f.tell()),行为都是相同的。

该问题似乎是由于所涉及的多层缓冲引起的。您得到的是一个io.TextIOWrapper包装了一个io.BufferedRandom(这反过来又包装了一个io.FileIO)。 TextIOWrapperio.BufferedRandom读取块以分摊从字节到文本的解码成本,因此,当您调用readline时,它实际上是在消耗并解码整个文件(它很小,适合一小块),将BufferedRandom放在文件末尾(即使从逻辑上讲,它应该只位于文件中间),TextIOWrapper.tell报告对应于该逻辑位置的位置。

当您转向write时,TextIOWrapper对数据进行编码并将其传递到BufferedRandom,后者仍然认为自己位于文件的末尾;由于TextIOWrapper不能更正此问题,因此数据将附加到最后。看似无操作的f.seek(f.tell())TextIOWrapper与下标BufferedRandom重新同步,以获得预期的行为。确实没有必要(我建议filing a bug确保write转到逻辑tell的位置,因为尽管Python 3 f.tell() gets out of sync with file pointer in binary append+read mode是表面上相似),但至少解决方法相对简单。

,

问题与缓冲的IO有关。

open()函数似乎打开了一个缓冲文件句柄。

因此,实际上,每当读取文件中的内容时,至少会读取整个缓冲区,该缓冲区似乎在我的计算机上约为8k(8192)字节。 这是为了优化性能。

因此readline将读取一个块,并返回第一行,并将其余的块保留在缓冲区中,以备将来读取。

f.tell()为您提供相对于readline()已返回的字节的位置。

这可以通过f.seek(f.tell())强制执行写指针  到您想要的地方。如果没有显式的seek语句,您将在缓冲区之后编写。

使用以下脚本来说明并查看输出:

您将看到,我尝试使用buffering参数。 根据文档1的意思是行缓冲,但是我看不到任何行为上的变化。

with open("file","w") as f:
    f.write(("*" * 79 +"\n") * 1000)

with open('file','r+',buffering=1) as f:
    print(f.tell())
    print(f.readline().strip())
    print(f.tell())
    # f.seek(f.tell())
    f.write('Hello')
    print(f.tell())

print("----------- file contents")
with open("file","r") as f:
    pass
    print(f.read())
print("----------- END")

因此,如果您在readline()之后进行写操作,则它将在读取的缓冲区之后写入新数据。

另一方面,

f.tell()返回您的位置,它告诉您已经返回了多少字节。

输出将是:

0
*******************************************************************************
80
8197
8202
----------- file contents
*******************************************************************************
*******************************************************************************
...
*******************************************************************************
********************************HelloHello*************************************
*******************************************************************************
*******************************************************************************
*******************************************************************************
*******************************************************************************
...
本文链接:https://www.f2er.com/3155504.html

大家都在问