文件模式:文本模式、二进制模式
一句话概述
Python 的 open() 函数通过模式参数(mode)决定如何处理文件:文本模式('r'/'w'/'a' 带 't')以字符串形式读写,自动处理编码;二进制模式('b')以原始字节形式读写,用于图片、音频、模型权重等非文本数据。
💡 核心要点:①文本模式(默认)自动处理字符编码,读写的是str字符串 ②二进制模式读写bytes字节,原样存取不翻译 ③'r'只读、'w'只写清空、'a'追加写、'x'排他创建 ④模式可以组合,如'rb'(二进制只读)、'wt'(文本只写)
教学与演示
一、文本模式(text mode)——人类的语言
是什么:文本模式是 open() 的默认模式('t' 可省略自动追加)。在此模式下,Python 会根据指定的 encoding(如 UTF-8)将硬盘上的字节自动解码为 Python 的 str 字符串(读),或将 str 编码为字节写入硬盘(写)。
大白话 就像你看一本用某种语言写的书——文本模式自动帮你"翻译"(编码/解码)。你只需要读写人类看得懂的字符串,Python 帮你搞定底层字节转换。UTF-8 就像是"世界通用翻译官"。
为什么:计算机硬盘上存储的一切都是 0 和 1(字节)。但人类需要的是文字。文本模式在中间做了一层翻译——读文件时把字节转成字符串(解码),写文件时把字符串转成字节(编码)。如果没有这层翻译,你读出来的是一堆数字,根本看不懂。
怎么做:
import numpy as np
# ===== 文本模式的本质:编码与解码 =====
# 文本模式 = 自动编码/解码,读写 str
# 模拟一段中文文本
original_text = "AI 全栈工程师\nPython 文件操作\n你好,世界!"
# 1. 编码(encode):str → bytes(模拟写文件时发生的事)
encoded_bytes = original_text.encode('utf-8')
print("【编码过程】str → bytes")
print(f"原始文本长度:{len(original_text)} 个字符")
print(f"编码后字节数:{len(encoded_bytes)} bytes")
print(f"字节内容(前20):{list(encoded_bytes[:20])}")
# 2. 解码(decode):bytes → str(模拟读文件时发生的事)
decoded_text = encoded_bytes.decode('utf-8')
print(f"\n【解码过程】bytes → str")
print(f"解码后文本:\n{decoded_text}")
# ===== 用 numpy 分析不同字符的编码长度 =====
chars = list("AI全栈hello")
char_bytes = np.array([len(c.encode('utf-8')) for c in chars])
print(f"\n各字符 UTF-8 编码字节数:{list(zip(chars, char_bytes))}")
print(f"英文字母:{np.mean(char_bytes[:2]):.0f} byte/字,中文字:{np.mean(char_bytes[2:4]):.0f} bytes/字")什么用:在 AI 开发中,文本模式是处理语料的基础——读取对话数据、清洗文本、分词前预处理,全都基于文本模式。NLP(自然语言处理)项目中,99% 的文件操作都是文本模式带 encoding='utf-8'。
二、二进制模式(binary mode)——机器的语言
是什么:在模式字符串中加入 'b'(如 'rb'、'wb')即开启二进制模式。此模式下,Python 不做任何编码转换,直接读写原始字节(bytes 类型)。文件内容是什么字节,读出来就是什么字节。
大白话 就像是直接复印原始手稿,不做翻译。二进制模式不管文件里是什么语言,原封不动地搬进搬出。你看不懂没关系,机器看得懂——图片、音频、视频、模型文件,都是用二进制模式读写的。
为什么:有些数据根本不是文本——图片的每个像素值、音频的波形采样、模型的浮点数权重。如果用文本模式去读,Python 会试图按 UTF-8 解码,遇到无效字节直接报错。二进制模式绕过了编码层,直接操作原始字节流。
怎么做:
import numpy as np
# ===== 二进制模式:直接操作 bytes =====
# 二进制模式读写的是 bytes,不做任何编码转换
# 1. 模拟一个"图片"的二进制数据
# 实际中这来自 open('photo.jpg', 'rb').read()
np.random.seed(42)
# 模拟 100 个像素的 RGB 值(每个 0-255)
pixel_data = np.random.randint(0, 256, (100, 3), dtype=np.uint8)
# 转换为 bytes(模拟二进制文件的原始内容)
binary_data = pixel_data.tobytes()
print("【二进制模式模拟】")
print(f"像素数组形状:{pixel_data.shape}(100 像素 × 3 通道 RGB)")
print(f"二进制数据大小:{len(binary_data)} bytes")
print(f"前 12 个字节(4 个像素):{list(binary_data[:12])}")
print(f" → 像素1 RGB=({binary_data[0]},{binary_data[1]},{binary_data[2]})")
print(f" → 像素2 RGB=({binary_data[3]},{binary_data[4]},{binary_data[5]})")
# 2. 从 bytes 恢复为 numpy 数组(模拟读二进制文件后解析)
recovered = np.frombuffer(binary_data, dtype=np.uint8).reshape(100, 3)
print(f"\n恢复后的数组形状:{recovered.shape}")
print(f"数据一致性检查:{np.array_equal(pixel_data, recovered)}")
# 3. 统计分析
print(f"\nR 通道统计:均值={np.mean(recovered[:,0]):.1f}, 标准差={np.std(recovered[:,0]):.1f}")
print(f"G 通道统计:均值={np.mean(recovered[:,1]):.1f}, 标准差={np.std(recovered[:,1]):.1f}")
print(f"B 通道统计:均值={np.mean(recovered[:,2]):.1f}, 标准差={np.std(recovered[:,2]):.1f}")什么用:在 AI 项目中,二进制模式是加载模型权重的必经之路。PyTorch 的 torch.load()、TensorFlow 的模型保存、图像数据的直接读取(open('img.png', 'rb')),全部依赖二进制模式。音视频处理、序列化对象(pickle)也都走二进制。
三、常见模式组合与对照——一张表看清所有模式
是什么:open() 的 mode 参数由 1-3 个字符组成:操作类型(r/w/a/x)+ 数据类型(t/b)+ 升级模式(+ 表示读写)。最常用的组合如下:
| 模式 | 含义 | 文件不存在 | 文件已存在 | 读写类型 |
|---|---|---|---|---|
'r' | 文本只读(默认) | 报错 | 从头读 | str |
'w' | 文本只写 | 创建 | 清空后写 | str |
'a' | 文本追加写 | 创建 | 从末尾追加 | str |
'x' | 文本排他创建 | 创建 | 报错 | str |
'rb' | 二进制只读 | 报错 | 从头读 | bytes |
'wb' | 二进制只写 | 创建 | 清空后写 | bytes |
'ab' | 二进制追加写 | 创建 | 从末尾追加 | bytes |
'r+' | 文本读写 | 报错 | 不截断 | str |
'w+' | 文本读写 | 创建 | 清空 | str |
大白话 就像是不同的钥匙开不同的锁——'r'是只读钥匙,'w'是覆盖写的钥匙,'a'是续写钥匙。选错钥匙,要么打不开门,要么把门里的东西全清空了。
为什么:不同场景需要不同的模式。读取配置文件用 'r',保存训练日志用 'a'(不能覆盖之前的内容),创建新文件且不想误覆盖用 'x',加载模型权重用 'rb'。理解每种模式的行为能避免数据丢失(比如误用 'w' 把重要文件清空了)。
怎么做:
import numpy as np
# ===== 模拟各种文件模式的行为对比 =====
def simulate_mode(mode_name, behavior, risk_level):
"""模拟不同文件打开模式的行为"""
return {"模式": mode_name, "行为": behavior, "风险等级": risk_level}
modes_info = [
simulate_mode("'r' 只读", "只能读,不能写。文件必须存在。", "★☆☆ 低"),
simulate_mode("'w' 只写", "只能写,自动清空旧内容。文件不存在则创建。", "★★★ 高(会清空数据!)"),
simulate_mode("'a' 追加", "只能写,在文件末尾追加,不破坏旧内容。", "★☆☆ 低"),
simulate_mode("'x' 排他创建", "只能创建新文件,若已存在则报错。防误覆盖。", "★☆☆ 低"),
simulate_mode("'rb' 二进制读", "以 bytes 读取,不做编码转换。用于图片/模型等。", "★☆☆ 低"),
simulate_mode("'wb' 二进制写", "以 bytes 写入,不做编码转换。保存模型权重。", "★★★ 高"),
simulate_mode("'r+' 读写", "可读可写,不截断文件。指针在开头。", "★★☆ 中"),
]
# 打印模式对照表
print(f"{'模式':<12} {'行为描述':<50} {'风险'}")
print("-" * 74)
for m in modes_info:
print(f"{m['模式']:<12} {m['行为']:<50} {m['风险等级']}")
# ===== 追加模式 vs 只写模式的数据对比 =====
print("\n--- 追加模式('a') vs 只写模式('w') 对比 ---")
# 模拟原始日志内容
original_log = ["Epoch 1: loss=2.3", "Epoch 2: loss=1.8", "Epoch 3: loss=1.2"]
# 'w' 模式:覆盖写入
with_w = ["Epoch 4: loss=0.9"] # 原内容全部丢失!
print(f"'w' 模式写入后只剩:{with_w}")
# 'a' 模式:追加写入
with_a = original_log + ["Epoch 4: loss=0.9"] # 原内容保留
print(f"'a' 模式写入后保留全部:{with_a}")
# 用 numpy 量化对比
w_scores = np.array([len(with_w), 0]) # w 模式:内容少,但丢了历史
a_scores = np.array([len(with_a), len(original_log)]) # a 模式:内容全
print(f"\n'w' 模式保留行数:{w_scores[0]}(原{len(original_log)}行全丢失!)")
print(f"'a' 模式保留行数:{a_scores[0]}(原{len(original_log)}行+新{a_scores[0]-len(original_log)}行)")什么用:在 AI 训练中,日志写入通常用 'a' 追加模式——每个 epoch 结束后追加一行记录,而不覆盖之前的记录。模型权重保存用 'wb' 二进制写模式。数据集划分后的保存用 'w' 或 'x'(防止误覆盖已有数据)。
四、编码问题实战——为什么中文会乱码?
是什么:字符编码是将人类文字映射为计算机字节的规则。常见编码:UTF-8(变长,兼容 ASCII,全球通用)、GBK(中文编码,Windows 中文版默认)、ASCII(只能表示 128 个英文字符)、Latin-1/ISO-8859-1(西欧语言)。用错误的编码读文件,就会出现乱码。
大白话 就像你用中文密码本去解密一份英文密码——肯定解出一堆乱码。编码是"翻译规则",UTF-8 是万能翻译官,GBK 是中文专用翻译官。读文件时用的编码必须和写文件时的编码一致。
为什么:同一个字节序列,用不同编码解读会得到不同的字符。比如中文字"你"在 UTF-8 中是 [0xE4, 0xBD, 0xA0] 三个字节,如果用 GBK 去解读这三个字节,就会变成别的字甚至报错。这就是为什么在 Windows 上用记事本保存的文件,到 Linux 上用默认 UTF-8 打开会乱码。
怎么做:
import numpy as np
# ===== 编码问题演示:同字节,不同解码 =====
# 一段中英文混合文本
sample_text = "Python文件操作——你好世界"
# 用不同编码编码同一段文本
utf8_bytes = sample_text.encode('utf-8')
try:
gbk_bytes = sample_text.encode('gbk')
except:
gbk_bytes = b''
print("【编码对比实验】")
print(f"原文:{sample_text}")
print(f"UTF-8 编码字节数:{len(utf8_bytes)}")
if gbk_bytes:
print(f"GBK 编码字节数:{len(gbk_bytes)}")
print(f"UTF-8 比 GBK 多 {len(utf8_bytes) - len(gbk_bytes) if gbk_bytes else 'N/A'} 字节(因为中文UTF-8占3字节,GBK占2字节)")
# ===== 用 numpy 分析字节分布 =====
utf8_arr = np.array(list(utf8_bytes), dtype=np.uint8)
print(f"\nUTF-8 字节分布:")
print(f" ASCII 范围(0-127):{np.sum(utf8_arr < 128)} 字节")
print(f" 多字节字符(>=128):{np.sum(utf8_arr >= 128)} 字节")
print(f" 说明:英文字母在 UTF-8 中占 1 字节(ASCII),中文占 3 字节")
# ===== 错误解码模拟 =====
print("\n【乱码模拟】")
# 用 latin-1 去解码 UTF-8 编码的中文
chinese = "你好"
ch_utf8 = chinese.encode('utf-8')
wrong_decode = ch_utf8.decode('latin-1') # 用错误编码解码
print(f"中文'{chinese}' UTF-8编码后用 latin-1 解码 → '{wrong_decode}' ← 乱码!")
# 正确做法
correct_decode = ch_utf8.decode('utf-8')
print(f"正确的 UTF-8 解码 → '{correct_decode}'")什么用:在 AI 数据预处理中,编码问题是最常见的坑。爬取的中文网页可能用 GBK,英文文档是 ASCII,训练语料可能是各种编码的混合。统一转换为 UTF-8、处理编码错误(errors='ignore' 或 'replace')是数据清洗的必备技能。open(file, encoding='utf-8', errors='ignore') 可以跳过无法解码的字节。
概念关系图谱
| 概念 | 核心含义 | 与AI的关系 | 关联概念 |
|---|---|---|---|
| 文本模式 | 自动编解码,读写 str 字符串 | NLP 语料读取、文本预处理 | 编码、UTF-8、字符串 |
| 二进制模式 | 原始字节读写,读写 bytes | 模型权重加载、图像读取、音频处理 | bytes、序列化 |
| UTF-8 | 变长 Unicode 编码,全球通用 | 训练语料统一编码标准 | 编码、解码、ASCII |
'w' 模式 | 清空后写入 | 保存清洗后的数据 | 文件模式、'a' 模式 |
'a' 模式 | 追加写入 | 训练日志持续记录 | 文件模式、'w' 模式 |
| 编码错误 | 用错编码导致的乱码 | 多语言语料的数据清洗 | UnicodeDecodeError、errors 参数 |
重点答疑
Q1:什么时候用文本模式,什么时候用二进制模式?
简单判断:如果文件内容可以用文本编辑器打开并看懂,用文本模式(.txt、.csv、.json、.py 等)。如果文件内容是图片、音频、视频、压缩包、模型文件等非文本格式,用二进制模式。口诀:人看得懂用文本,机器才懂用二进制。
Q2:'w' 和 'a' 的区别是什么?什么时候用哪个?
'w'(write)打开文件时先清空再写入,适合覆盖式保存(如保存最新配置)。'a'(append)打开文件时保留原内容,在末尾追加,适合累加式记录(如训练日志、聊天记录)。用错 'w' 会导致历史数据丢失,这个坑很多新手踩过。
Q3:Python 读取文件时默认编码是什么?
Python 3 中 open() 的默认编码取决于操作系统——Linux/Mac 通常是 UTF-8,Windows 中文版默认是 GBK(或 cp936)。这就是为什么同一段代码在 Mac 上正常、到 Windows 上就中文乱码。强烈建议每次都显式指定 encoding='utf-8',保证跨平台一致性。
章节单词汇总
| 英文 | 音标 | 术语/释义 |
|---|---|---|
| mode | /moʊd/ | 模式;指定文件打开方式(读/写/追加等) |
| binary | /ˈbaɪnəri/ | 二进制;由 0 和 1 组成的数据形式 |
| bytes | /baɪts/ | 字节;计算机存储的最小单位 |
| encode | /ɪnˈkoʊd/ | 编码;字符串转为字节的过程 |
| decode | /diːˈkoʊd/ | 解码;字节转为字符串的过程 |
| append | /əˈpend/ | 追加;在文件末尾添加内容 |
| truncate | /ˈtrʌŋkeɪt/ | 截断;清空文件原有内容 |
| Unicode | /ˈjuːnɪkoʊd/ | 统一码;涵盖全球所有字符的编码标准 |
面试练习
Q1 [单选] open('data.txt', 'wb') 中的 'wb' 表示什么?
- A. 文本只写
- B. 二进制只写
- C. 文本读写
- D. 二进制追加写
解答:'wb'= write binary,二进制只写模式。'w'表示写操作,'b'表示二进制模式。
Q2 [单选] 如果文件不存在,以下哪个模式会报错?
- A.
'w' - B.
'a' - C.
'r' - D.
'x'
解答:'r'(只读)要求文件必须存在,不存在则抛出FileNotFoundError。'w'和'a'在文件不存在时会自动创建。'x'在文件存在时报错,不存在时创建。
Q3 [单选] 中文字符"你"在 UTF-8 编码下占几个字节?
- A. 1 字节
- B. 2 字节
- C. 3 字节
- D. 4 字节
解答:常见中文字符在 UTF-8 中占 3 个字节。英文字母和数字占 1 字节,某些 emoji 占 4 字节。这是 UTF-8 "变长编码"的特点。
Q4 [多选] 以下哪些场景应该使用二进制模式?
- A. 读取 JPEG 图片文件
- B. 加载 PyTorch 模型权重(.pt 文件)
- C. 读取 CSV 训练数据
- D. 播放 MP3 音频文件
解答:图片、模型权重、音频都是二进制格式,需要用'rb'模式。CSV 是文本格式,用文本模式'r'即可。
Q5 [单选] open('log.txt', 'a') 中 'a' 的含义是?
- A. 清空文件后写入
- B. 只能读不能写
- C. 在文件末尾追加内容
- D. 创建新文件(存在则报错)
解答:'a'(append)在文件末尾追加内容,不破坏已有内容。这是记录日志时的常用模式。
Q6 [单选] 以下哪个模式组合可以让文件既可读又可写,且不截断原有内容?
- A.
'r+' - B.
'w+' - C.
'rw' - D.
'wr'
解答:'r+'以读写模式打开,文件必须存在且不会被截断。'w+'会先清空文件。'rw'和'wr'不是有效模式。
Q7 [单选] 用错误的编码读文件会怎样?
- A. 什么都不会发生
- B. 抛出
UnicodeDecodeError或产生乱码 - C. Python 会自动检测并纠正编码
- D. 文件会被自动转换为正确编码
解答:用错误编码解码时,Python 会抛出UnicodeDecodeError(除非用errors='ignore'跳过),或得到乱码字符串。Python 不会自动检测编码。
Q8 [单选] open('data.bin', 'xb') 中的 'x' 的作用是什么?
- A. 可执行模式
- B. 排他创建——文件存在则报错
- C. 加密模式
- D. 扩展模式
解答:'x'(exclusive creation)是排他创建模式:文件不存在则创建,文件已存在则抛出FileExistsError。适用于防止误覆盖已有文件的安全场景。
Q9 [多选] 关于文本模式和二进制模式,以下哪些说法正确?
- A. 文本模式自动处理编码转换
- B. 二进制模式读写的是
bytes类型 - C. 文本模式下不能写入中文字符
- D. 二进制模式下不能使用
encoding参数
解答:C 错误——文本模式配合encoding='utf-8'完全可以写入中文。D 正确——二进制模式下指定encoding会报错,因为二进制模式不做编码转换。
Q10 [单选] 跨平台开发时,为保证中文文件不乱码,建议怎么做?
- A. 不指定 encoding,让 Python 自动选择
- B. 统一使用
encoding='gbk' - C. 统一使用
encoding='utf-8' - D. 将文件后缀改为
.unicode
解答:UTF-8 是互联网和开源社区的事实标准编码,跨平台兼容性最好。所有现代系统都支持 UTF-8。显式指定 encoding='utf-8' 可以避免默认编码不一致导致的乱码。