在"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

这似乎是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.BufferedRandomio.TextIOWrapper(这反过来又包裹了一个io.FileIO)。TextIOWrapperio.BufferedRandom中读取块以分摊从字节解码为文本的成本,因此当您调用readline时,它实际上是在消耗和解码整个文件(它是如此之小,可以放入一个块),BufferedRandom位于文件的末尾(即使从逻辑上讲它应该只在中途, 并TextIOWrapper.tell报告与该逻辑位置相对应的位置)。

当你转身write时,TextIOWrapper对数据进行编码并将其传递给BufferedRandom,仍然认为自己在文件的末尾;由于TextIOWrapper没有纠正这一点,数据被粘在最后。看似无操作f.seek(f.tell())TextIOWrapper与下属BufferedRandom重新同步,以获得预期的行为。这应该不是真的必要(我建议提交一个错误以确保write进入逻辑tell位置,因为我找不到现有的错误,尽管 Python 3 f.tell() 在二进制追加 + 读取模式下与文件指针不同步表面上相似),但至少解决方法相对简单。

该问题与缓冲 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*************************************
*******************************************************************************
*******************************************************************************
*******************************************************************************
*******************************************************************************
...

最新更新