文件模式:文本模式、二进制模式

一句话概述

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' 可以避免默认编码不一致导致的乱码。