python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Python seek与tell函数

Python文件操作之文件指针seek与tell函数用法全解

作者:星河耀银海

本文将深入讲解Python中文件操作的核心函数seek()和tell(),阐述文件指针的概念、tell()获取当前字节偏移位置、seek()在二进制与文本模式下的不同行为及限制,并通过获取文件大小、读取最后N行、随机访问记录等实战案例,希望对大家有所帮助

一、开篇:文件中隐藏的"光标"

当你打开一个文件,Python在幕后维护着一个"文件指针"(file pointer)——它标记了你当前在文件中的位置。每次read()或write(),指针都会移动。tell()告诉你指针在哪,seek()把指针移到指定位置。

直观感受:

with open("demo.txt", "w", encoding="utf-8") as f:
    f.write("ABCDEFGHIJ")  # 10个字符

with open("demo.txt", "r", encoding="utf-8") as f:
    print(f"打开时位置: {f.tell()}")       # 0 —— 开头
    print(f"读3个字符: {f.read(3)}")       # ABC
    print(f"现在位置: {f.tell()}")         # 3
    print(f"再读2个: {f.read(2)}")         # DE
    print(f"现在位置: {f.tell()}")         # 5

    # 回到开头
    f.seek(0)
    print(f"seek(0)后位置: {f.tell()}")    # 0
    print(f"重新读: {f.read(5)}")          # ABCDE

二、tell() —— 我在哪?

# tell() 返回当前文件指针的位置(字节偏移量)
# 文本模式下:返回的是"不透明"位置——只能用于seek()
# 二进制模式下:返回准确的字节偏移量

# 文本模式——tell()返回的值不一定是字符数
with open("demo.txt", "r", encoding="utf-8") as f:
    f.read(5)
    pos = f.tell()
    print(f"文本模式tell: {pos}")

# 二进制模式——tell()返回精确的字节数
with open("demo.txt", "rb") as f:
    f.read(5)
    pos = f.tell()
    print(f"二进制模式tell: {pos}")  # 5 —— 准确的字节数

# 💡 在二进制模式下,tell()的值可以和seek()精确配合
# 在文本模式下,tell()的值只能用于seek()恢复到同一位置

三、seek() —— 跳到任意位置

3.1 seek()的基本语法

# seek(offset, whence)
# offset: 偏移量(整数,可正可负)
# whence: 参考点
#   0 —— 从文件开头(默认)
#   1 —— 从当前位置
#   2 —— 从文件末尾

# 准备测试数据
with open("seek_demo.bin", "wb") as f:
    f.write(b"0123456789ABCDEF")  # 16字节

# === whence=0:从开头 ===
with open("seek_demo.bin", "rb") as f:
    f.seek(5, 0)          # 跳到第5字节(从开头数)
    print(f.read(3))       # b'567' —— 第5,6,7字节

    f.seek(0)              # 回到开头
    print(f.read(4))       # b'0123'

# === whence=1:从当前位置 ===
with open("seek_demo.bin", "rb") as f:
    f.read(4)              # 读4字节 —— 位置现在是4
    f.seek(2, 1)           # 从当前位置跳2字节 —— 位置变成6
    print(f"位置: {f.tell()}")  # 6
    print(f.read(2))       # b'67'

# === whence=2:从末尾 ===
with open("seek_demo.bin", "rb") as f:
    f.seek(0, 2)           # 跳到末尾
    print(f"文件大小: {f.tell()}")  # 16

    f.seek(-4, 2)          # 倒数第4字节
    print(f.read())        # b'CDEF'

3.2 文本模式下的seek限制

# ⚠️ 文本模式只能:
# - seek(0) —— 回到开头
# - seek(0, 2) —— 跳到末尾
# - seek(某个tell()返回的值) —— 恢复到之前位置

with open("demo.txt", "r", encoding="utf-8") as f:
    f.read(3)
    saved_pos = f.tell()      # 保存位置
    f.read(3)
    f.seek(saved_pos)         # 恢复到保存的位置
    print(f.read(3))          # 重新从保存位置读取

# ❌ 文本模式下不能:
# f.seek(5)       # 不确定行为——offset如果不是0,必须来自tell()
# f.seek(5, 1)    # 不能从当前位置偏移
# f.seek(-5, 2)   # 不能从末尾偏移

# ✅ 二进制模式下完全自由——seek()任意位置

四、实战应用

4.1 获取文件大小

def get_file_size(filepath):
    """获取文件大小——使用seek到末尾"""
    with open(filepath, "rb") as f:
        f.seek(0, 2)   # 到末尾
        return f.tell() # 当前位置=文件大小

# 或者直接:
import os
size = os.path.getsize("seek_demo.bin")
print(f"文件大小: {size}字节")

4.2 读取文件的最后N行

def tail(filepath, n=10):
    """模拟Linux的tail命令——读取文件最后n行"""
    with open(filepath, "rb") as f:
        # 跳到末尾
        f.seek(0, 2)
        file_size = f.tell()

        # 从末尾倒着读
        buffer = bytearray()
        chunk_size = 1024
        lines_found = 0
        pos = file_size

        while pos > 0 and lines_found <= n:
            read_size = min(chunk_size, pos)
            pos -= read_size
            f.seek(pos)
            chunk = f.read(read_size)
            buffer[0:0] = chunk  # 插到前面
            lines_found = buffer.count(b'\n')

        # 解码并取最后n行
        text = buffer.decode("utf-8", errors="ignore")
        lines = text.splitlines()
        return lines[-n:] if len(lines) > n else lines

# 测试
# with open("test.txt", "w") as f:
#     for i in range(100):
#         f.write(f"第{i+1}行\n")
# print(tail("test.txt", 5))

4.3 随机访问记录文件

import struct

# 固定长度记录文件——可以用seek()实现O(1)随机访问
# 每条记录:ID(4字节) + 姓名(20字节) + 分数(4字节) = 28字节

RECORD_SIZE = 28
RECORD_FORMAT = "i20sf"  # int, 20字节字符串, float

def write_record(f, record_id, name, score):
    """写入一条固定长度记录"""
    name_bytes = name.encode("utf-8")[:20].ljust(20, b'\0')
    data = struct.pack(RECORD_FORMAT, record_id, name_bytes, score)
    f.write(data)

def read_record(f, index):
    """随机读取第index条记录(O(1))"""
    f.seek(index * RECORD_SIZE)
    data = f.read(RECORD_SIZE)
    if len(data) < RECORD_SIZE:
        return None
    record_id, name_bytes, score = struct.unpack(RECORD_FORMAT, data)
    name = name_bytes.decode("utf-8").rstrip('\0')
    return {"id": record_id, "name": name, "score": score}

# 创建记录文件
with open("records.dat", "wb") as f:
    write_record(f, 1, "张三", 85.5)
    write_record(f, 2, "李四", 92.0)
    write_record(f, 3, "王五", 78.5)
    write_record(f, 4, "赵六", 88.0)

# 随机访问第3条记录(索引2,因为从0开始)
with open("records.dat", "rb") as f:
    record = read_record(f, 2)
    print(record)  # {'id': 3, 'name': '王五', 'score': 78.5}

    # 直接跳到第0条
    record = read_record(f, 0)
    print(record)  # {'id': 1, 'name': '张三', 'score': 85.5}

五、总结

文件指针让你能精确控制在文件中的位置。tell()查位置,seek()移位置——两者配合让你实现随机访问。

核心要点:

  1. tell() —— 获取当前字节偏移
  2. seek(offset, whence) —— 跳到指定位置
  3. 文本模式 —— seek受限(只能跳到开头/末尾/tell()位置)
  4. 二进制模式 —— seek完全自由,精确到字节
  5. seek(0, 2) + tell() 获取文件大小

需要随机访问、跳过/回退内容时——切到二进制模式,seek()让你随心所欲。

到此这篇关于Python文件操作之文件指针seek与tell函数用法全解的文章就介绍到这了,更多相关Python seek与tell函数内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文