Python 文件指针是什么?seek 和 tell 怎么用?
简化版
文件指针表示当前读写位置。tell() 返回当前位置,seek(offset, whence) 移动位置;文本文件中通常只安全使用 seek(0) 或使用 tell() 返回的位置,二进制文件更适合做任意偏移的随机访问。
详细版
每个打开的文件对象都有当前位置:
with open("data.txt", "r", encoding="utf-8") as f:
print(f.tell())
print(f.read(5))
print(f.tell())
f.seek(0)
print(f.read(5))
seek() 常见参数:
f.seek(0) # 回到开头
f.seek(10) # 移到某个位置
f.seek(0, 2) # 移到末尾,二进制模式更常用
whence 的含义:
| whence | 含义 |
|---|---|
0 | 从文件开头计算,默认 |
1 | 从当前位置计算 |
2 | 从文件末尾计算 |
需要注意:文本模式涉及编码和换行转换,偏移不一定等于“字符数量”。如果要按字节精确定位,应该用二进制模式。
完整版教学
一、文件读写为什么需要当前位置
文件可以看成一条很长的数据流,文件对象内部维护一个“当前读写位置”。每次读写都会影响这个位置。
with open("sample.txt", "w+", encoding="utf-8") as f:
f.write("abcdef")
print(f.read()) # 读不到内容
为什么读不到?因为写完后位置在文件末尾。要重新从开头读,必须:
with open("sample.txt", "w+", encoding="utf-8") as f:
f.write("abcdef")
f.seek(0)
print(f.read())
这也是 + 模式容易出错的根源之一:你既读又写,就必须知道当前位置在哪里。
二、tell 返回的是什么
tell() 返回文件当前位置。二进制模式下,它通常可以理解为从文件开头开始的字节偏移。
with open("data.bin", "rb") as f:
f.read(10)
pos = f.tell()
print(pos)
文本模式下,tell() 返回的是一个可用于 seek() 的位置标记,不应该简单理解为字符下标。因为文本模式可能发生编码解码和换行转换,一个字符可能占多个字节。
三、seek 的三种定位方式
seek(offset, whence) 用来移动当前位置。
从开头开始:
f.seek(0)
f.seek(100)
从当前位置或末尾计算,在二进制模式里更常见:
with open("data.bin", "rb") as f:
f.seek(0, 2) # 到末尾
size = f.tell()
f.seek(-100, 2) # 读取最后 100 个字节
tail = f.read()
注意:文本模式下不支持所有形式的相对定位。为了避免跨平台、编码和换行带来的问题,面试中可以明确说“任意字节偏移应使用二进制模式”。
四、seek 的常见应用
第一类是重复读取:
with open("config.txt", "r", encoding="utf-8") as f:
first = f.read()
f.seek(0)
second = f.read()
第二类是读取文件尾部,例如简单实现 tail:
with open("app.log", "rb") as f:
f.seek(0, 2)
end = f.tell()
f.seek(max(0, end - 4096))
data = f.read()
第三类是固定格式文件随机访问,比如某些二进制索引文件、定长记录文件。
五、文本文件随机访问为什么麻烦
假设字符串中有中文:
abc你好
字符数量和字节数量不是一回事。UTF-8 中一个中文通常占 3 个字节。如果你按“第 4 个字符”去套字节偏移,可能刚好跳到一个多字节字符中间,导致解码失败。
因此处理文本内容时,通常优先顺序读取、逐行解析。只有在已经保存了 tell() 的位置标记,或者明确知道底层编码与边界时,才进行定位。
六、常见误区与追问
心法:
tell()记录当前位置,seek()改变当前位置。对二进制文件可以把它理解成字节偏移;文本文件因为编码和换行转换,随机定位要更谨慎。
- 误区:
tell()在文本模式下一定等于字符数。 文本模式下返回的是不透明位置标记,不应简单当作字符下标;多字节编码和换行转换会让字符数与字节偏移不同。 - 追问:
seek(offset, whence)的whence有哪些值?0表示从文件开头,1表示从当前位置,2表示从文件末尾;文本模式下非零相对偏移支持受限,二进制模式更适合随机访问。 - 误区:读完文件后再次读取会自动从头开始。 文件对象有当前位置,读到末尾后继续读会得到空内容;要重新读需要
seek(0)。 - 追问:为什么大文件尾部读取常用二进制模式? 从文件末尾按字节倒退更可控,避免文本解码边界和换行转换干扰;找到边界后再解码文本更稳。
- 误区:
seek会改变文件内容。seek只移动当前位置,不写入数据;真正改变内容的是后续write、截断等操作。 - 追问:读写混合模式为什么容易出错? 在
r+、w+等模式中,读写共享同一个位置,读后写、写后读通常要配合seek或flush,否则位置和缓冲状态容易混乱。
七、加强记忆
文件指针就是“下一次读写从哪里开始”。read()、write() 会移动它,tell() 告诉你当前位置,seek() 让你跳到指定位置。文本模式关注字符语义,随机偏移容易被编码和换行影响;二进制模式关注字节,更适合精确定位。