4. 文件指针与定位:tell()、seek() 与三种移动模式

文件指针这东西,说白了就是文件内部的一个「书签」。你读到哪里,它就跟到哪里。我刚开始学文件操作时,总觉得它很抽象,后来在做一个日志分析工具时,才真正体会到它的重要性。

当时我需要反复读取同一个文件的某一段内容。如果每次都要从头读到尾,那效率太低了。嗯,这时候文件指针的定位功能就派上了大用场。

4.1 tell() 方法——看看指针在哪儿

tell() 方法返回文件指针的当前位置。单位是字节。你想想看,就像你读书时手指指着的那个位置,tell() 就是告诉你手指现在在第几行。

with open('example.txt', 'r', encoding='utf-8') as f:
    content = f.read(5)   # 读取前5个字符
    position = f.tell()   # 看看指针现在在哪儿
    print(f'当前指针位置:{position}')

我在项目中遇到过一个问题:用 tell() 获取位置后,想跳回去重新读。结果发现位置不对。后来才意识到,不同编码下,一个中文字符占的字节数不一样。UTF-8 下,一个中文占 3 个字节。所以 tell() 返回的字节数,和你肉眼看到的字符数,往往对不上。

小提示:文本模式下,tell() 返回的是字节偏移量,不是字符偏移量。二进制模式下,它返回的就是实实在在的字节位置。

4.2 seek() 方法——让指针跳到你想要的位置

seek() 方法用来移动文件指针。它的语法很简单:

file.seek(offset, whence)

offset 是偏移量,whence 是参考位置。我习惯把 whence 理解为「从哪儿开始算」。0 表示从文件开头算,1 表示从当前位置算,2 表示从文件末尾算。

举个例子:

with open('data.bin', 'rb') as f:
    # 跳到第10个字节
    f.seek(10, 0)
    data = f.read(4)
    print(data)

    # 从当前位置往后跳5个字节
    f.seek(5, 1)
    data = f.read(4)
    print(data)

    # 从文件末尾往前跳8个字节
    f.seek(-8, 2)
    data = f.read(4)
    print(data)

我曾经在做一个二进制文件解析器时,需要跳过文件头部的 64 字节元数据,直接读取数据区。用 seek(64, 0) 一步到位,省去了逐字节读取的麻烦。

4.3 三种移动模式(0/1/2)

这三种模式,说白了就是三种参考系。我整理了一张表,方便你对照:

模式 常量名 含义 offset 取值
0 os.SEEK_SET 从文件开头计算偏移 必须为非负数
1 os.SEEK_CUR 从当前位置计算偏移 可以为负数(往前移)
2 os.SEEK_END 从文件末尾计算偏移 通常为负数(往前移)
注意:文本模式下(即打开文件时没有加 'b'),seek() 只能使用模式 0,而且 offset 只能是 0 或 tell() 返回的值。如果你想自由移动指针,请使用二进制模式。

为什么会这样?因为文本模式下,Python 会做一些换行符的转换(比如 Windows 下的 \r\n 转成 \n),导致字节数和实际位置对不上。所以 Python 干脆限制了文本模式下的 seek 行为。

4.4 实战:用文件指针实现断点续读

我记得有一次写一个日志监控程序,需要记录上次读到的位置,下次启动时接着读。这就是典型的断点续读场景。

import os

def read_log_with_resume(log_file, position_file):
    # 读取上次记录的位置
    if os.path.exists(position_file):
        with open(position_file, 'r') as pf:
            last_pos = int(pf.read().strip())
    else:
        last_pos = 0

    # 从上次位置继续读取
    with open(log_file, 'r', encoding='utf-8') as f:
        f.seek(last_pos, 0)
        new_content = f.read()
        current_pos = f.tell()

    # 记录当前位置
    with open(position_file, 'w') as pf:
        pf.write(str(current_pos))

    return new_content

# 使用示例
new_logs = read_log_with_resume('app.log', 'position.txt')
print(new_logs)

这个例子中,我们用 seek() 跳到最后一次读取的位置,用 tell() 记录当前读取结束的位置。下次再调用时,就能无缝衔接。

4.5 知识体系总览

下面这张图,帮你把文件指针的核心逻辑串起来:

文件指针与定位核心逻辑 文件指针 tell() seek(offset, whence) 三种模式 0/1/2 返回当前指针位置 单位:字节 移动指针到指定位置 offset:偏移量 0: 文件开头 1: 当前位置 | 2: 文件末尾 断点续读、随机访问、二进制解析

这张图把 tell()、seek() 和三种模式的关系画清楚了。你想想看,掌握了这三个东西,文件指针就完全在你掌控之中了。

核心要点:
  • tell() 告诉你指针在哪儿,seek() 让指针去你想去的地方
  • 三种模式 0/1/2 对应三种参考系:开头、当前位置、末尾
  • 文本模式下 seek 有限制,二进制模式下才能自由移动
  • 文件指针是实现断点续读、随机访问的关键

嗯,文件指针这部分就讲到这里。记住一句话:指针在哪儿,读写就在哪儿。搞清楚了指针的位置,文件操作就成功了一半。

可可精品资料库