文件操作的核心不是记住某一个方法,而是先打开文件得到一个文件对象,再围绕这个文件对象的文件指针进行读取、写入、移动和关闭。
可以先记住这条线:
perl
体验AI代码助手
代码解读
复制代码
open(...) -> 得到文件对象 -> read / readline / readlines / for / write / seek / flush -> close
日常代码里更推荐使用 with open(...) as file:,因为代码块结束时会自动关闭文件,也会把缓冲区里还没写入文件的数据处理掉。
一、纯文本文件和二进制文件
文件最终都以二进制形式存储在磁盘上。我们平时说"文本文件"和"二进制文件",区别不在于磁盘底层是不是二进制,而在于程序读写时是否按字符编码把二进制数据转换成人能直接阅读的文本。
纯文本文件
纯文本文件在读取和写入时,需要遵循某种字符编码规范,比如 UTF-8。Python 会按照编码把磁盘上的字节解码成字符串,也会把字符串编码成字节再写回磁盘。
纯文本文件最终呈现出来的是可以直接阅读的文本信息。常见例子有:
text
体验AI代码助手
代码解读
复制代码
.txt .py .md .html .json .csv
写纯文本文件时,建议明确指定编码:
python
体验AI代码助手
代码解读
复制代码
with open('note.txt', 'rt', encoding='utf-8') as file: content = file.read()
二进制文件
二进制文件读写时不涉及字符编码。Python 不会把内容解码成字符串,而是直接读出 bytes 数据。
二进制文件通常需要能识别格式的软件进行解析,最后呈现形式可能是音频、视频、图片、文档、幻灯片等。常见例子有:
体验AI代码助手
代码解读
复制代码
.mp3 .mp4 .doc .ppt .jpg .png .pdf
读二进制文件时要带上 b:
python
体验AI代码助手
代码解读
复制代码
with open('image.png', 'rb') as file: data = file.read()
这里的 data 是 bytes,不是 str。
二、open 函数和 mode 组合
open 用来打开或创建文件,返回值是文件对象。最常用的三个参数是:
| 参数 | 作用 |
|---|---|
file |
要操作的文件路径 |
mode |
文件打开模式,决定能不能读、能不能写、是否清空、是否追加 |
encoding |
字符编码,文本模式常用 utf-8 |
python
体验AI代码助手
代码解读
复制代码
# 假设 a.txt 内容是:hello # 1. 打开文件,得到文件对象 file file = open('a.txt', 'rt', encoding='utf-8') # 2. 从文件对象里读取内容 content = file.read() # content == 'hello' # 3. 用完后关闭文件 file.close()
更推荐写成:
python
体验AI代码助手
代码解读
复制代码
# 假设 a.txt 内容是:hello with open('a.txt', 'rt', encoding='utf-8') as file: content = file.read() # 代码块结束后,文件会自动关闭 # content == 'hello'
with 小知识
with 可以理解成"进入代码块时打开文件,离开代码块时自动收尾"。文件操作里的收尾最重要的就是 close()。
如果手动写 open 和 close,中间代码一旦报错,file.close() 可能就执行不到:
python
体验AI代码助手
代码解读
复制代码
file = open('a.txt', 'rt', encoding='utf-8') content = file.read() file.close()
写成 with 后,不管代码块是正常执行完,还是中间发生异常,Python 都会帮你关闭文件:
python
体验AI代码助手
代码解读
复制代码
with open('a.txt', 'rt', encoding='utf-8') as file: content = file.read()
所以日常读写文件时,优先用 with open(...) as file:。它不是新的读取方式,真正读写文件的仍然是 file.read()、file.write() 这些方法;with 只是帮你把关闭文件这一步做得更稳。
mode 不是随便拼字符串,它由三类信息组合出来:
css
体验AI代码助手
代码解读
复制代码
基础操作:r / w / x / a 数据类型:t / b 更新能力:+
基础操作
| 模式 | 含义 | 文件不存在 | 文件已存在 | 指针初始位置 |
|---|---|---|---|---|
r |
只读 | 报错 | 正常打开 | 文件开头 |
w |
写入 | 创建文件 | 清空原内容 | 文件开头 |
x |
排他性创建 | 创建文件 | 报错 | 文件开头 |
a |
追加写入 | 创建文件 | 保留原内容 | 文件末尾 |
r 是默认值,所以 open('a.txt') 等价于 open('a.txt', 'rt')。
文本模式和二进制模式
| 模式 | 含义 | 读出来的类型 | 写入时需要的类型 | 是否使用 encoding |
|---|---|---|---|---|
t |
文本模式,默认值 | str |
str |
是 |
b |
二进制模式 | bytes |
bytes |
否 |
t 是默认值,所以 r、w、a 实际上分别等价于 rt、wt、at。
二进制模式不要传 encoding:
python
体验AI代码助手
代码解读
复制代码
with open('music.mp3', 'rb') as file: data = file.read()
加号模式
+ 表示"更新模式",也就是同一个文件对象既能读又能写。它必须依附在 r、w、x、a 其中一个基础模式上。
| 模式 | 能力 | 关键特点 |
|---|---|---|
r+ / rt+ |
读写文本 | 文件必须存在,不清空,指针在开头 |
w+ / wt+ |
读写文本 | 文件不存在则创建,文件存在则先清空 |
x+ / xt+ |
读写文本 | 文件必须不存在,已存在就报错 |
a+ / at+ |
读写文本 | 文件不存在则创建,写入总是追加到末尾 |
rb+ |
读写二进制 | 文件必须存在,不清空,指针在开头 |
wb+ |
读写二进制 | 文件不存在则创建,文件存在则先清空 |
xb+ |
读写二进制 | 文件必须不存在,已存在就报错 |
ab+ |
读写二进制 | 文件不存在则创建,写入总是追加到末尾 |
rt+ 这种写法可以理解为:
diff
体验AI代码助手
代码解读
复制代码
r:以读取为基础打开 t:按文本处理 +:允许读和写
因此 rt+ 适合"文件已经存在,我想读它,也可能改它"的场景。
选择模式时可以按下面这条路径判断:
rust
体验AI代码助手
代码解读
复制代码
只读已有文件 -> r / rt / rb 重新生成一个文件 -> w / wt / wb 文件必须是新文件,不能覆盖旧文件 -> x / xt / xb 只想把内容加到末尾 -> a / at / ab 既要读又要写 -> 在上面的基础上加 +
最容易踩坑的是 w 和 w+:只要文件已经存在,打开瞬间就会清空原内容。不是等到 write 执行时才清空。
三、读取文件:指针会一直往前走
文件对象内部有一个"文件指针"。读取不是每次都从头开始,而是从指针所在位置继续向后读。每读走一段,指针就向后移动一段。
可以把它想成一根只能向前推进的指针:
scss
体验AI代码助手
代码解读
复制代码
文件内容:abcdefg 指针: ^ read(2) -> ab 指针: ^ read(3) -> cde 指针: ^ read() -> fg 指针: ^
read(size)
read 读取文件内容。size 是可选参数。
| 写法 | 含义 |
|---|---|
file.read() |
从指针位置读取到文件末尾 |
file.read(10) |
从指针位置最多读取 10 个字符或 10 个字节 |
文本模式下,size 表示字符数量;二进制模式下,size 表示字节数量。
python
体验AI代码助手
代码解读
复制代码
# 假设 a.txt 内容是:abcdefg with open('a.txt', 'rt', encoding='utf-8') as file: r1 = file.read(2) # 从开头读 2 个字符:ab r2 = file.read(3) # 接着读 3 个字符:cde r3 = file.read(4) # 只剩 fg,所以只能读到:fg r4 = file.read() # 已经到文件末尾了,所以读到空字符串:'' print(r1, end='') print(r2, end='') print(r3, end='') print(r4, end='') # 最终输出:abcdefg
如果已经读到文件末尾,再继续 read,会返回空字符串:
python
体验AI代码助手
代码解读
复制代码
# 假设 a.txt 内容是:hello with open('a.txt', 'rt', encoding='utf-8') as file: content = file.read() # 第一次已经把 hello 全部读完 empty = file.read() # 第二次从文件末尾继续读,只能得到 '' print(empty == '') # True
二进制模式下读到末尾后返回的是 b''。
读取大文件时,不要直接 read() 一次性读完。更适合分块读取:
python
体验AI代码助手
代码解读
复制代码
with open('big.txt', 'rt', encoding='utf-8') as file: while True: # 每次最多读取 1024 个字符,不一次性把整个文件读进内存 chunk = file.read(1024) # 文本文件读到末尾时,read 会返回空字符串 '' if chunk == '': break print(chunk, end='')
readline(size)
readline 读取当前这一行。size 是可选参数。
| 写法 | 含义 |
|---|---|
file.readline() |
读取指针所在行,通常包含行尾换行符 |
file.readline(10) |
还是只读当前这一行,但最多读 10 个字符或 10 个字节 |
注意:readline(10) 的 10 不是"读取 10 行",而是"当前这一行最多读 10 个字符或字节"。
连续读取时可以这样理解:
python
体验AI代码助手
代码解读
复制代码
# 假设 a.txt 内容是: # abcdefg # 第二行 with open('a.txt', 'rt', encoding='utf-8') as file: print(file.readline(3)) # abc,第一行还没读完 print(file.readline(3)) # def,继续读第一行 print(file.readline()) # g\n,读完第一行剩下的内容 print(file.readline()) # 第二行\n,才开始读第二行
前两次都还在第一行里读。因为第一行还没有读完,下一次 readline() 会继续从文件指针当前位置往后读,而不是自动跳到下一行。
python
体验AI代码助手
代码解读
复制代码
# 假设 a.txt 内容是: # 第一行 # 第二行 # 第三行 with open('a.txt', 'rt', encoding='utf-8') as file: r1 = file.readline() # '第一行\n' r2 = file.readline() # '第二行\n' r3 = file.readline() # '第三行\n' r4 = file.readline() # 已经到文件末尾,所以是 '' print(r1.strip()) print(r2.strip()) print(r3.strip()) print(r4.strip())
strip() 会去掉字符串两边的空白字符,包括换行、空格、制表符。如果只想去掉行尾换行,更稳妥的是:
python
体验AI代码助手
代码解读
复制代码
print(line.rstrip('\n'))
循环逐行读取:
python
体验AI代码助手
代码解读
复制代码
with open('a.txt', 'rt', encoding='utf-8') as file: while True: # 每次只读一行 line = file.readline() # 读到文件末尾时,line 会变成空字符串 '' if line == '': break # rstrip('\n') 只去掉行尾换行符,不会去掉普通空格 print(line.rstrip('\n'))
直接遍历文件对象
文件对象本身可以被 for 循环遍历,每次拿到一行。这是读取文本文件最常用、也比较省内存的写法。
python
体验AI代码助手
代码解读
复制代码
# 假设 a.txt 内容是: # 第一行 # 第二行 # 第三行 with open('a.txt', 'rt', encoding='utf-8') as file: for line in file: # 第 1 次循环:line == '第一行\n' # 第 2 次循环:line == '第二行\n' # 第 3 次循环:line == '第三行\n' print(line, end='')
这里不需要自己判断 line == '',for 循环会在文件结束时自动停止。
readlines(hint)
readlines 会一次性按行读取,返回一个列表。
python
体验AI代码助手
代码解读
复制代码
# 假设 a.txt 内容是: # 第一行 # 第二行 # 第三行 with open('a.txt', 'rt', encoding='utf-8') as file: # readlines() 会把每一行放进列表 lines = file.readlines() print(lines) # ['第一行\n', '第二行\n', '第三行\n']
hint 是可选参数,表示"读到的总字符数或总字节数差不多超过这个值后,就不要继续读更多行了"。它不是行数,也不是严格上限,因为 readlines 会按"整行"放进列表,不会为了凑 hint 把一行切断。
python
体验AI代码助手
代码解读
复制代码
with open('a.txt', 'rt', encoding='utf-8') as file: # 读到的总长度大致超过 100 后,就不再继续读更多行 lines = file.readlines(100)
读取时大概是这种感觉:
python
体验AI代码助手
代码解读
复制代码
# 假设 a.txt 内容是: # abc # def # ghi with open('a.txt', 'rt', encoding='utf-8') as file: # 这里的 5 不是 5 行,而是总长度提示 # 读完 'abc\n' 后长度是 4,还没超过 5 # 再读一整行 'def\n' 后长度是 8,超过 5,于是停止 lines = file.readlines(5) print(lines) # 可能是 ['abc\n', 'def\n']
这里的 5 不是 5 行。第一行 'abc\n' 长度是 4,还没超过 5;再读一整行 'def\n' 后总长度变成 8,已经超过 5,于是停止继续读。因为它按整行返回,所以结果总长度可以超过 hint。
readlines() 会把多行内容放进列表,不适合读取体积很大的文件。大文件优先用 for line in file 或分块 read(size)。
四、写入文件:先到缓冲区,再落到文件
write 用来写入内容。文本模式写入 str,二进制模式写入 bytes。
python
体验AI代码助手
代码解读
复制代码
with open('demo.txt', 'wt', encoding='utf-8') as file: file.write('你好1') file.write('你好2') # demo.txt 最终内容是:你好1你好2 # write 不会自动换行
write 不会自动加换行。想换行要自己写 \n:
python
体验AI代码助手
代码解读
复制代码
with open('demo.txt', 'wt', encoding='utf-8') as file: file.write('你好1\n') file.write('你好2\n') # demo.txt 最终内容是: # 你好1 # 你好2
write 有返回值,表示本次写入了多少个字符或字节:
python
体验AI代码助手
代码解读
复制代码
with open('demo.txt', 'wt', encoding='utf-8') as file: count = file.write('你好') # 写入 2 个字符 print(count) # 2
文件写入时,并不是每调用一次 write 就一定立刻写入磁盘。Python 和操作系统中间可能有缓冲区。缓冲区的作用是减少频繁写磁盘的成本。
lua
体验AI代码助手
代码解读
复制代码
write(...) -> 先写入缓冲区 -> 缓冲区满了 / flush / close / with 结束 -> 再写入文件
如果希望把缓冲区里的内容立刻推给文件对象,可以调用 flush():
python
体验AI代码助手
代码解读
复制代码
import time with open('demo.txt', 'at', encoding='utf-8') as file: file.write('你好1') file.write('你好2') # 立刻刷新缓冲区,方便其他程序尽快看到这部分内容 file.flush() time.sleep(10) # flush 之后文件没有关闭,还可以继续写 file.write('你好3') file.write('你好4')
flush() 不是"关闭文件",文件还能继续写。close() 才是关闭文件。使用 with 时,代码块结束会自动关闭文件。
常见写入模式的差别:
python
体验AI代码助手
代码解读
复制代码
# 覆盖写入:旧内容会被清空 with open('demo.txt', 'wt', encoding='utf-8') as file: file.write('新的内容\n') # demo.txt 只剩:新的内容 # 追加写入:新内容写到文件末尾 with open('demo.txt', 'at', encoding='utf-8') as file: file.write('追加内容\n') # demo.txt 变成:新的内容 + 追加内容 # 排他性创建:文件已存在会报错,避免误覆盖 with open('new.txt', 'xt', encoding='utf-8') as file: file.write('只允许创建新文件\n') # 如果 new.txt 已经存在,这里会抛 FileExistsError
五、seek 和 tell:显式移动文件指针
read、readline、write 都会影响文件对象的指针。默认情况下,读写会从指针位置继续往后走。如果要主动改变指针,可以用 seek。
python
体验AI代码助手
代码解读
复制代码
file.seek(offset, whence)
| 参数 | 含义 |
|---|---|
offset |
偏移量,要移动多少距离 |
whence |
参考点,从哪里开始计算偏移 |
whence 有三个常用值:
| 值 | 含义 |
|---|---|
0 |
从文件开头计算,默认值 |
1 |
从指针位置计算 |
2 |
从文件末尾计算 |
tell() 可以查看指针位置:
python
体验AI代码助手
代码解读
复制代码
# 假设 a.txt 是二进制读取,内容是:abcdefg with open('a.txt', 'rb') as file: print(file.tell()) # 0,刚打开时指针在开头 data = file.read(5) # 读走 b'abcde' print(data) # b'abcde' print(file.tell()) # 5,指针移动到了第 5 个字节后面
作者:copyer_xyf
链接:https://juejin.cn/post/7648120903580958763
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。