Python 文件 I/O 中 file.seek() 和 file.read() 指针行为
我遇到了一个问题,结果和我预期的不一样。
这两段代码的区别在于一个是file.seek(3,0),另一个是file.read(3)。
在这两种情况下,我都确认文件指针在位置3,但当我写入文件时,得到的结果却不一样。有人能解释一下这是为什么吗?
#=====Code Snippet #1===========
# original file has "hi world!"
with open(filename, 'r+') as file:
file.seek(3,0) # we move the file pointer to index 3
print(file.tell()) # prints 3
file.write("friend!")
# file now has "hi friend!" <--- AS EXPECTED
# ======Code Snippet #2===========
# original file has "hi world!"
with open(filename, 'r+') as file:
file.read(3) # We read characters at index 0,1,2 and move the file pointer to index 3
print(file.tell()) # prints 3
file.write("friend!")
# file now has "hi world!friend!" <--- NOT AS EXPECTED
# =====================================
2 个回答
你打开文件时使用的是默认的“文本”模式,因为没有加“b”来表示二进制模式,比如用 open(file, "rb+")
。文本模式是默认的(你也可以用“t”来表示,比如 open(file, "rt+")
),这意味着所有的文件输入输出都是以文本为主的,读取时会进行缓冲处理。
所以,尽管 .tell
方法可以告诉你光标的位置,也就是从哪里开始读取,但操作系统层面的文件指针其实是在文件的末尾,写入会从那里继续。
没错,这种行为确实不太对,但这是因为在“文本”模式下,用“seek”来精确定位文件位置并在文件中间写入是从来没有被期望能正常工作的。原因很简单,这种模式下最明显的变化就是行结束符的转换(在Windows上,\r\n
这个两个字节会被无缝转换成一个\n
字符)。所以,在文本文件中定位并不被认为是确定的(而且你遇到的额外缓冲读取也证明了这一点)。
因此,解决你问题的“变通方法”就是使用二进制模式来处理文件,因为这是唯一一个被期望能在文件中精确定位并读取/写入字节的模式:
In [51]: filename = "file.txt"
In [52]: open(filename, "wb").write(b"hi world!")
Out[52]: 9
In [54]: # original file has "hi world!"
...: with open(filename, 'rb+') as file:
...: file.read(3) # We read characters at index 0,1,2 and move t
...: he file pointer to index 3
...: print(file.tell()) # prints 3
...: file.write(b"friend!")
...: # file now has "hi world!friend!"
...:
3
In [55]: open(filename).read()
Out[55]: 'hi friend!'
(另外要注意,在二进制模式下打开的文件中,你必须写入字节对象(带有“b”前缀),而不是文本)
在读取和写入之间,你需要使用一个叫做 seek
的操作:
import os
with open(filename, 'r+') as file:
file.read(3)
file.seek(0, os.SEEK_CUR)
file.write("friend!")
可惜的是,我觉得Python的文档里没有提到这一点。C语言的文档里有类似的说明,但我认为Python的这个要求完全没有记录。
需要注意的是,使用可变宽度的文本编码时,试图在文件中间写入N个字符并不一定能保证会覆盖掉N个已有的字符。如果你不小心,可能会导致覆盖掉一部分字符,从而损坏你的文件。