4. 文件指针与定位:tell()、seek()方法,文件指针的移动与重置
好,咱们继续聊文件操作。前面我们讲了怎么打开文件、怎么读写,但有个细节你可能没注意到——文件指针。
说白了,文件指针就像你在书里夹的书签。你读到哪一页,书签就停在哪儿。下次再打开,接着往下读。Python 里的文件对象,内部就维护着这样一个「书签」。每次读写,书签都会自动往后移动。
我刚开始学的时候,总觉得这东西很抽象。直到有一次写日志分析脚本,发现读出来的数据总是少了一半……嗯,后来才明白是文件指针跑偏了。
4.1 文件指针是什么?
当你用 open() 打开一个文件时,Python 会在内存里创建一个文件对象。这个对象里有个隐藏的计数器,记录着当前读写位置距离文件开头的字节数。
- 刚打开文件时,指针在位置 0(文件开头)
- 每读一个字节,指针就往后挪一个字节
- 每写一个字节,指针也往后挪一个字节
你想想看,如果不知道指针在哪儿,你怎么知道读到哪了?
4.2 tell() —— 问问指针在哪儿
tell() 方法返回当前指针的位置(从文件开头算起的字节数)。
我个人习惯在调试文件操作时,到处打印一下指针位置。这样能清楚地看到读写过程发生了什么。
# 打开一个文件
with open('demo.txt', 'w', encoding='utf-8') as f:
f.write('Hello, 世界!')
with open('demo.txt', 'r', encoding='utf-8') as f:
print(f'tell() 初始位置:{f.tell()}') # 0
content = f.read(5)
print(f'读取了:{content}')
print(f'tell() 读取后位置:{f.tell()}') # 5(因为 'Hello' 是5个字节)
# 继续读
rest = f.read()
print(f'剩余内容:{rest}')
print(f'tell() 读完位置:{f.tell()}') # 文件末尾
注意:中文字符在 UTF-8 编码下占 3 个字节。所以「世界」两个字占了 6 个字节。如果你用 tell() 看到的位置和预期不一样,先检查编码。
4.3 seek() —— 让指针跳来跳去
seek() 方法可以移动文件指针。它有两个参数:
| 参数 | 说明 |
|---|---|
offset |
偏移量(字节数),正数向后移,负数向前移 |
whence |
起始位置:0=文件开头,1=当前位置,2=文件末尾 |
说白了,seek(offset, whence) 就是告诉 Python:「从 whence 位置开始,移动 offset 个字节」。
with open('demo.txt', 'r', encoding='utf-8') as f:
# 跳到第 7 个字节(从文件开头算起)
f.seek(7, 0)
print(f'当前位置:{f.tell()}') # 7
print(f'读取:{f.read()}') # 世界!
# 回到开头
f.seek(0, 0)
print(f'回到开头后读取:{f.read()}') # Hello, 世界!
我在项目中遇到过一个问题:用 seek() 在文本文件中跳来跳去,结果读出来的中文全是乱码。为什么?因为 seek() 是按字节跳的,而中文字符是多字节的。你跳到某个字节的中间位置,自然就乱码了。
4.4 常用场景:重新读取文件
有时候你读了一遍文件,想再读一遍。最直接的办法是重新 open(),但用 seek(0) 更高效。
with open('config.txt', 'r', encoding='utf-8') as f:
# 第一遍读
first_pass = f.read()
print(f'第一遍:{first_pass}')
# 指针回到开头
f.seek(0)
# 第二遍读
second_pass = f.read()
print(f'第二遍:{second_pass}')
嗯,这里要注意:seek(0) 只是把指针移回开头,文件本身没有重新打开。所以效率比重新 open() 高得多。
4.5 知识体系图
下面这张图帮你理清文件指针的核心逻辑:
4.6 实战:文件指针的完整示例
最后,我给你一个完整的例子。把前面讲的东西串起来:
# 创建一个测试文件
with open('test.txt', 'w', encoding='utf-8') as f:
f.write('0123456789\n')
f.write('ABCDEFGHIJ\n')
f.write('abcdefghij\n')
# 演示指针操作
with open('test.txt', 'r', encoding='utf-8') as f:
# 读第一行
line1 = f.readline()
print(f'第一行:{line1.strip()}') # 0123456789
print(f'当前指针:{f.tell()}') # 11(10个数字 + 1个换行符)
# 跳到第 15 个字节
f.seek(15)
print(f'跳转后指针:{f.tell()}') # 15
print(f'读取:{f.read(5)}') # CDEFG
# 回到开头
f.seek(0)
print(f'回到开头后读取全部:{f.read()}')
输出结果:
第一行:0123456789
当前指针:11
跳转后指针:15
读取:CDEFG
回到开头后读取全部:0123456789
ABCDEFGHIJ
abcdefghij
看到了吧?指针就像个灵活的游标。你可以用它来精确定位,也可以用它来重新读取。掌握好 tell() 和 seek(),文件操作就灵活多了。
好了,文件指针这块就聊到这儿。记住三个关键点:tell() 问位置,seek() 跳位置,读写操作自动移位置。下次遇到文件读不全的问题,先检查一下指针在哪儿。