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