1
2
3
4
5
6
7
作者:李晓辉

联系方式:

1. 微信:Lxh_Chat

2. 邮箱:939958092@qq.com
  1. Python 系列(一) 从零开始认识 Python,入门其实没那么难
  2. Python 系列(二) 学 Python 的第一步,把开发环境搭建好
  3. Python 系列(三) 基础语法避坑指南,缩进、大小写、续行与注释
  4. Python 系列(四) Python 输入输出详解,print()、input() 与常见避坑指南
  5. Python 系列(五) 数值类型与运算符详解,整除、浮点精度与进制转换实战
  6. Python 系列(六) 字符串处理实战,查找、替换、切片与日志分析
  7. Python 系列(七) 类型转换详解,int()、float()、str() 与金额计算避坑
  8. Python 系列(八) Python 流程控制入门,掌握 if、elif 和 else
  9. Python 系列(九) Python 逻辑运算符详解,and、or、not 一篇搞懂
  10. Python 系列(十) while、for 循环实战,彻底搞懂 range、break 与 continue
  11. Python 系列(十一) list、tuple、set、dict,到底该怎么选?
  12. Python 系列(十二) List 列表实战,搞定数据增删改查与批量处理
  13. Python 系列(十三) Tuple 元组实战,搞懂不可变特性与 enumerate() 遍历
  14. Python 系列(十四) Set 集合实战,搞定数据去重与集合运算
  15. Python 系列(十五) Dict 字典详解,掌握快速查找与键值操作
  16. Python 系列(十六) sort() 和 sorted() 详解,掌握数据排序技巧
  17. Python 系列(十七) 自定义函数入门,让重复代码一次编写多次使用
  18. Python 系列(十八) 函数参数详解,搞懂位置参数、关键字参数和默认参数
  19. Python 系列(十九) 从 *args 到 **kwargs,让函数调用更灵活
  20. Python 系列(二十) 变量作用域是什么?一文搞懂 global 和 nonlocal
  21. Python 系列(二十一) 搞懂变量、对象和引用,理解函数参数传递机制
  22. Python 系列(二十二) map() 和 filter() 详解,轻松实现批量数据处理与筛选
  23. Python 系列(二十三) 嵌套函数与 lambda 表达式详解,搞懂作用域与闭包
  24. Python 系列(二十四) 搞懂递归函数,从基线条件到实际应用
  25. Python 系列(二十五) 聊聊模块与 import,让代码告别重复编写
  26. Python 系列(二十六) Python 模块入门,从 import 到模块复用
  27. Python 系列(二十七) Python dir() 函数详解,快速查看模块中的函数与属性
  28. Python 系列(二十八) 不会用 Python 标准库?教你快速查找模块和函数
  29. Python 系列(二十九) 搞懂 Python sys 模块,让脚本更适合自动化运维
  30. Python 系列(三十) 为什么 0.1 + 0.2 不等于 0.3?彻底搞懂 Decimal
  31. Python 系列(三十一) 搞懂 datetime 日期时间,轻松处理时区与时间计算
  32. Python 系列(三十二) 面向对象编程入门,从零理解类、对象、封装与继承
  33. Python 系列(三十三) 什么是类和对象?零基础理解 Python class
  34. Python 系列(三十四) Python 属性详解,搞懂类属性、实例属性和 setter
  35. Python 系列(三十五) 学会特殊方法,让对象打印出你想要的内容
  36. Python 系列(三十六) 多个对象如何共享数据?彻底搞懂类变量
  37. Python 系列(三十七) 搞懂父类、子类和方法重写,真正理解继承与多态
  38. Python 系列(三十八) 如何判断数据类型?三个内置函数一次讲明白
  39. Python 系列(三十九) 搞懂异常模型与常见异常类型,读懂程序报错
  40. Python 系列(四十) 从零学会异常处理,掌握 try、except、else 和 finally
  41. Python 系列(四十一) ValueError、TypeError 到底是什么?一文搞懂常见异常
  42. Python 系列(四十二) ValueError、TypeError 到底是什么?一文搞懂常见异常
  43. Python 系列(四十三) 别只会处理异常!学会自定义异常,让程序更容易维护
  44. Python 系列(四十四) 从零认识 assert 断言,让程序学会检查自己
  45. Python 系列(四十五) 使用 sys 模块处理标准输入与输出
  46. Python 系列(四十六) 使用 open() 读取和写入文件
  47. Python 系列(四十七) 学会读写文本文件,让程序保存运行结果
  48. Python 系列(四十八) 搞懂 bytes、bytearray 和二进制读写
  49. Python 系列(四十九) 使用 seek() 和 tell() 灵活控制文件读取位置
  50. Python 系列(五十) 掌握 os 和 os.path,实现文件与目录自动化管理
  51. Python 系列(五十一) 掌握列表推导式,轻松实现列表生成与数据筛选
  52. Python 系列(五十二) 掌握字典推导式,让字典创建更简单
  53. Python 系列(五十三) 从零理解生成器,轻松处理大量数据
  54. Python 系列(五十五) 使用 zip() 同时遍历多个集合

前面我们学习了如何使用 Python 打开文件、读取文本、写入内容,以及处理二进制文件。

在这些操作中,我们经常会使用 read() 方法读取文件内容。例如,读取一个日志文件,查看里面记录的服务器运行情况。

但是,如果文件内容比较长,我们可能会遇到这样的需求:

  • 文件已经读取了一部分,想回到开头重新读取,怎么办?
  • 只想从文件的某个位置开始读取,而不是每次都从头开始,怎么办?
  • 读取文件的过程中,想知道当前已经读到了什么位置,怎么办?

如果每次都重新打开文件,再从头读取,不仅操作繁琐,也可能做很多不必要的工作。

Python 提供了两个非常实用的方法:seek() 和 tell()。

  • seek():移动文件的读取或写入位置。
  • tell():查看当前文件位置。

学会这两个方法,我们就可以更灵活地控制文件读写过程。

理解文件指针:Python 是从哪里开始读取的?

在学习 seek() 和 tell() 之前,我们需要先理解一个概念:文件指针。

这里的文件指针,并不是我们用鼠标操作的光标,而是 Python 在处理文件时,用来记录当前读写位置的一个概念。

可以把它想象成一本书里的书签。

假设一本书有 100 页,我们先读完第 10 页。如果想继续往下读,就从第 11 页开始;如果想重新阅读第 1 页,就需要把书签移回开头。

Python 读取文件时,也有类似的机制。

flowchart TD
    A["打开文件"] --> B["文件位置:开头"]
    B --> C["读取一部分内容"]
    C --> D["文件位置向后移动"]
    D --> E{"接下来想做什么?"}
    E -->|继续读取| F["从当前位置继续"]
    E -->|重新读取| G["使用 seek() 返回指定位置"]
    E -->|查看位置| H["使用 tell() 获取当前位置"]

例如,我们创建一个名为 demo.txt 的文件,内容如下:

1
2
3
Hello
Python
Linux

注意,文件中的每一行末尾通常还包含一个换行符。换行符也是文件内容的一部分,会占用相应的位置。

接下来,我们编写一个简单的程序,看看 Python 是如何记录文件读取位置的。

1
2
3
4
5
6
7
with open("demo.txt", "r", encoding="utf-8") as f:
print(f.tell())

data = f.read(5)
print(data)

print(f.tell())

运行结果:

1
2
3
0
Hello
5

为什么会得到这些结果?

  • 第一次调用 tell(),返回 0,表示当前位于文件开头。
  • f.read(5) 读取 5 个字符,也就是 Hello。
  • 读取完成后,文件位置向后移动,因此再次调用 tell(),返回 5。

这里的 read(5) 表示读取最多 5 个字符,而不是读取 5 行。

在这个示例中,文件内容是纯英文字符,所以读取 5 个字符后,位置也是 5。

对于文本文件,尤其是包含中文等多字节字符的文件,tell() 返回的位置值不能简单地理解为已经读取了多少个字符。它返回的是一个可用于恢复文本流位置的定位值。

理解这一点之后,我们就可以开始学习两个方法了。

使用 tell() 查看当前文件位置

tell() 的作用很简单:告诉我们当前文件的读写位置在哪里。

基本语法如下:

1
position = f.tell()

其中:

  • f 是通过 open() 打开的文件对象。
  • tell() 用于获取当前位置。
  • position 是我们自己定义的变量,用于保存返回的位置值。

我们继续使用前面的 demo.txt 文件。

1
2
3
4
5
6
7
8
with open("demo.txt", "r", encoding="utf-8") as f:
print(f"初始位置:{f.tell()}")

print(f.read(5))
print(f"读取后的位置:{f.tell()}")

print(f.read(6))
print(f"再次读取后的位置:{f.tell()}")

运行结果:

1
2
3
4
5
6
初始位置:0
Hello
读取后的位置:5

Python
再次读取后的位置:12

为什么第二次读取后的位置是 12?

第一次读取了 Hello,共 5 个字符。

接着,read(6) 从当前位置继续读取 6 个字符。文件中 Hello 后面有一个换行符,再接着是 Python。

因此,这 6 个字符是:

1
2

Python

其中包含 1 个换行符和 5 个英文字母?这里需要特别注意:Python 本身有 6 个英文字母,所以 read(6) 实际读取的是换行符以及 Pytho,而不是完整的 Python。

为了避免把换行符的影响和位置计算混在一起,我们将示例调整为更直观的内容。

创建 simple.txt,写入以下内容,注意不要在内容末尾额外添加换行符:

1
ABCDEFGHIJ

运行下面的程序:

1
2
3
4
5
6
7
8
with open("simple.txt", "r", encoding="utf-8") as f:
print(f"初始位置:{f.tell()}")

print(f.read(3))
print(f"当前位置:{f.tell()}")

print(f.read(2))
print(f"当前位置:{f.tell()}")

运行结果:

1
2
3
4
5
初始位置:0
ABC
当前位置:3
DE
当前位置:5

这个例子就非常直观了。

  • 开始时,位置是 0。
  • 读取 ABC 后,位置移动到 3。
  • 再读取 DE 后,位置移动到 5。

需要注意,tell() 只负责查看位置,不会主动读取文件内容,也不会改变当前的位置。

使用 seek() 移动文件位置

了解如何查看文件位置之后,我们再来看如何主动改变它。

seek() 可以让我们把文件位置移动到指定位置,而不必一直从当前位置向后读取。

基本语法如下:

1
f.seek(offset)

其中,offset 表示定位时使用的位置偏移量。它的具体含义取决于定位方式以及文件打开模式。

对于普通文本文件,最常见的用法是:

1
f.seek(0)

它会把文件位置恢复到开头。

我们还是使用 simple.txt 文件,内容为:

1
ABCDEFGHIJ

编写以下程序:

1
2
3
4
5
6
with open("simple.txt", "r", encoding="utf-8") as f:
print(f.read(3))

f.seek(0)

print(f.read(3))

运行结果:

1
2
ABC
ABC

第一次调用 read(3),读取了 ABC。

接着,f.seek(0) 将文件位置重新移动到开头。

所以第二次调用 read(3) 时,又读取到了 ABC。

整个过程可以这样理解:

flowchart TD
    A["文件开头:位置 0"] --> B["读取 ABC"]
    B --> C["当前位置:3"]
    C --> D["seek(0)"]
    D --> E["返回文件开头"]
    E --> F["再次读取 ABC"]

这就是 seek() 最常见的用途:让文件从指定位置重新开始读取。

从文件的指定位置开始读取

除了返回文件开头,我们还可以把文件位置移动到其他位置。

继续使用内容为 ABCDEFGHIJ 的 simple.txt。

1
2
3
4
with open("simple.txt", "r", encoding="utf-8") as f:
f.seek(5)

print(f.read())

运行结果:

1
FGHIJ

为什么从 F 开始?

因为在这个只包含英文字符的示例中,每个字符都占据一个简单的位置:

1
2
字符: A B C D E F G H I J
位置: 0 1 2 3 4 5 6 7 8 9

调用 f.seek(5) 后,文件位置移动到 F 所在的位置。

之后调用 read(),Python 就会从 F 开始,一直读取到文件末尾。

需要注意:这里的位置从 0 开始计算,而不是从 1 开始计算。

对于这个示例,seek(0) 对应 A,seek(5) 对应 F。

不过,这种按数字直接定位的方式主要适用于简单的文本示例。真实文本文件可能包含中文、换行符或其他编码内容,因此不能总是把偏移量当作字符数量使用。

使用 seek() 和 tell() 记录并恢复位置

有时候,我们并不知道接下来需要返回文件的哪个位置。

这时可以先用 tell() 记录当前位置,之后再用 seek() 返回这个位置。

例如,我们读取一段内容后,想暂时去其他位置查看数据,随后再回到之前的位置继续处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
with open("simple.txt", "r", encoding="utf-8") as f:
print(f.read(3))

# 记录当前文件位置
position = f.tell()
print(f"记录的位置:{position}")

# 移动到文件开头
f.seek(0)
print(f"重新读取:{f.read(2)}")

# 恢复之前记录的位置
f.seek(position)
print(f"从原位置继续:{f.read(3)}")

运行结果:

1
2
3
4
ABC
记录的位置:3
重新读取:AB
从原位置继续:DEF

我们按照执行顺序来理解。

首先,f.read(3) 读取 ABC,当前位置变为 3。

接着,position = f.tell() 将当前位置保存到变量 position 中。

然后,f.seek(0) 返回文件开头,读取 AB。

此时文件位置已经发生变化,但变量 position 中保存的仍然是之前记录的值 3。

最后,执行 f.seek(position),Python 就会恢复到之前记录的位置,从 D 开始继续读取。

这里需要记住一个关键点:变量 position 保存的是之前获取的位置值,不会随着文件位置变化而自动改变。

这种先记录位置、再恢复位置的方式,在需要反复检查文件内容时非常有用。

在 Linux 运维中,什么时候会用到它们?

学习 Python 不只是为了记住几个方法,更重要的是知道它们能解决什么问题。

假设我们需要编写一个程序,读取服务器生成的日志文件。

日志文件可能包含大量记录,例如:

1
2
3
4
2026-10-21 10:00:01 INFO nginx started
2026-10-21 10:00:02 INFO request received
2026-10-21 10:00:03 WARNING high cpu usage
2026-10-21 10:00:04 ERROR connection failed

如果只是读取文件开头的部分内容,可以使用:

1
2
with open("app.log", "r", encoding="utf-8") as f:
print(f.read(50))

如果希望再次检查文件开头的内容,就可以使用:

1
2
3
4
5
6
7
8
with open("app.log", "r", encoding="utf-8") as f:
print("第一次读取:")
print(f.read(50))

f.seek(0)

print("重新读取:")
print(f.read(50))

这段程序演示了如何重新读取文件开头。

不过,在真正的日志分析工作中,如果需要查找 ERROR、统计异常数量或逐行处理日志,通常会使用 for 循环、字符串方法或其他更适合的工具,而不是频繁移动文件位置。

seek() 和 tell() 更适合需要明确控制文件位置的场景,例如:

  • 读取文件的一部分后重新读取。
  • 记录当前位置,稍后恢复。
  • 在支持定位的文件中读取指定位置附近的数据。
  • 在二进制文件处理中,根据文件结构定位特定数据。

需要注意,seek() 并不意味着所有文件都能像数据库一样高效地随机访问。对于普通磁盘文件,定位通常比较直接;对于管道、终端等不可随意定位的流,定位操作可能不受支持。

文本文件和二进制文件的定位有什么区别?

前面我们分别学习了文本文件和二进制文件。两种文件都可以使用 seek() 和 tell(),但它们的定位规则有所不同。

文本文件

通常通过下面的方式打开:

1
2
3
with open("simple.txt", "r", encoding="utf-8") as f:
print(f.read(3))
print(f.tell())

文本模式下,tell() 返回的位置值可能是一个用于恢复文件位置的特殊定位值。不要总是把它当作字符数量,也不要随意对它进行加减运算。

最稳妥的常见用法是保存 tell() 的返回值,再将这个值传给 seek(),恢复之前的位置。

二进制文件

使用 rb 模式打开:

1
2
3
with open("data.bin", "rb") as f:
print(f.read(3))
print(f.tell())

二进制模式下,读取操作直接处理字节。tell() 返回的是从文件开头计算的字节位置,seek() 也可以按照字节偏移量定位。

例如,文件内容为:

1
ABCDEFGHIJ

使用二进制模式读取:

1
2
3
4
5
6
with open("data.bin", "rb") as f:
print(f.read(3))

f.seek(5)

print(f.read(2))

运行结果:

1
2
b'ABC'
b'FG'

这里的 b'ABC' 和 b'FG' 是 bytes 类型的数据显示形式。

因为每个英文字母在这个示例中占一个字节,所以从位置 5 开始读取两个字节,得到 FG。

对于包含中文的 UTF-8 文件,一个汉字通常占用多个字节。因此,二进制模式下不能把字节位置直接当作字符位置。

使用 seek() 时需要注意什么?

学习这两个方法时,有几个容易出错的地方值得特别注意。

不要把位置编号和字符编号混为一谈。

在纯英文示例中,字符位置与字节位置看起来一致。但对于中文文本,二者可能不同。

文本模式下,不要随意计算定位值。

如果需要返回之前的位置,推荐先保存 tell() 的返回值,再传给 seek():

1
2
position = f.tell()
f.seek(position)

不要默认所有文本定位值都可以直接进行加减运算。

不是所有文件流都支持任意定位。

普通磁盘文件通常支持定位,但管道、终端或其他非定位流可能不支持。遇到不支持定位的情况时,调用 seek() 可能会引发 OSError。

使用 with open() 管理文件。

推荐始终使用 with 语句打开文件。代码块执行结束后,Python 会自动关闭文件,减少资源管理方面的问题。

总结

今天我们学习了两个控制文件位置的方法:

方法作用示例
tell()查看当前文件位置f.tell()
seek()移动到指定文件位置f.seek(0)

记住最重要的区别:

  • tell() 负责查看位置,不会主动移动位置。
  • seek() 负责移动位置,让后续读取或写入从指定位置开始。
  • tell() 获取的位置值可以保存下来,在适当的条件下通过 seek() 恢复。
  • 文本模式和二进制模式的定位规则不同,需要根据文件类型正确使用。

对于初学者来说,可以先熟练掌握 f.seek(0),理解它如何让文件重新从开头读取,再逐步练习记录和恢复文件位置。

写在最后

到这里,我们已经学习了 Python 文件操作中的打开文件、文本读写、二进制读写,以及使用 seek() 和 tell() 控制文件位置。

下一篇,我们将学习 Python 的 os 和 os.path 模块,了解如何使用 Python 查看目录、判断文件是否存在、获取文件信息,以及创建和管理目录。

这些操作会让我们的 Python 程序逐渐具备自动处理文件和目录的能力,为后续编写 Linux 运维自动化脚本打下基础。