有一个这样的文本文件:
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.BufferedRandom
的io.TextIOWrapper
(这反过来又包裹了一个io.FileIO
)。TextIOWrapper
从io.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*************************************
*******************************************************************************
*******************************************************************************
*******************************************************************************
*******************************************************************************
...