文件读写时出现乱码,是几乎所有 Python 开发者都踩过的坑。这个问题看似麻烦,但只要理解了“字符编码”的基本原理,就能轻松规避。
乱码是怎么产生的?
简单说,乱码的根源是 “编码(写入时)”和“解码(读取时)”使用了不一致的字符集。例如,一个文件用 UTF-8 编码保存了中文,但用 GBK 去读取,就会看到一堆看不懂的符号。
计算机只能存储二进制数字,字符需要按一套规则转换成数字(编码),读取时再按同一套规则把数字转回字符(解码)。当编码和解码的规则不一样时,数字对应的字符就完全错乱。
Python 里的真实表现
在 Python 中,字符串分为两种:
- str(文本字符串):内存中的 Unicode 字符,可以表示所有国家的文字。
- bytes(字节串):原始字节数据,比如从磁盘读出来的未经解码的内容。
当你打开一个文件时,如果指定了不正确的编码,Python 就可能在解码或编码时出错,表现可能是:
- 直接抛出
UnicodeDecodeError或UnicodeEncodeError,程序中断。 - 采用“忽略”或“替换”策略后,读出来的内容全是问号或乱码,静默失败,更难排查。
实用解决方案与最佳实践
1. 始终显式指定编码
写代码时不要依赖系统的默认编码(不同操作系统默认编码不同),打开文件时明确使用 encoding='utf-8'。UTF-8 是当下最通用的编码,能覆盖全球字符。
# 不推荐:不指定编码,可能出乱码
with open('data.txt', 'r') as f:
content = f.read()
# 推荐:显式指定 UTF-8 编码
with open('data.txt', 'r', encoding='utf-8') as f:
content = f.read()
2. 处理未知编码的文件
如果你拿到一个来源不明的文件,不知道它的编码,可以尝试用 chardet 库自动检测。这不是百分百准确,但在多数场景下足够实用。
import chardet
with open('unknown.txt', 'rb') as f:
raw_data = f.read()
result = chardet.detect(raw_data)
encoding = result['encoding'] # 例如 'GB2312' 或 'utf-8'
with open('unknown.txt', 'r', encoding=encoding) as f:
text = f.read()
3. 写入文件时也指定编码
写入文本同样要显式指定编码,确保生成的文件能被正确读取。
with open('output.txt', 'w', encoding='utf-8') as f:
f.write('中文内容\n')
4. 处理非法字符
有时文件里夹杂了少量不符合编码规则的“坏数据”,可以设置 errors 参数让程序跳过或替换,而不是崩溃。
# 忽略无法解码的字节(谨慎使用,可能会丢失数据)
with open('data.txt', 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
# 将无法解码的字节替换为 � (U+FFFD)
with open('data.txt', 'r', encoding='utf-8', errors='replace') as f:
content = f.read()
需要提醒的是,errors='ignore' 会静默丢弃数据,尽量只在确实无法修复的容错场景下使用。
5. 统一项目编码规范
在团队中,约定所有源代码文件、配置文件、数据文件都用 UTF-8 编码存储。编辑器中设置默认编码为 UTF-8(无 BOM),避免不同操作系统和工具带来的差异。
常见踩坑场景与应对
- Windows 记事本默认保存为 ANSI(GBK):用 Python 读取这类文件时一定要指定
encoding='gbk',或者先用其他工具另存为 UTF-8。 - Excel 导出的 CSV 中文乱码:CSV 文件经常会被 Excel 写成
GBK编码,Pandas 读取时也要指定编码:
import pandas as pd
df = pd.read_csv('file.csv', encoding='gbk')
- API 返回的 JSON 数据乱码:HTTP 响应中的 JSON 通常默认是 UTF-8,但偶尔会有例外。用
response.json()时若报编码错误,可先取原始字节再手动解码:
data = response.content.decode('utf-8')
一句话总结
永远显式指定 encoding='utf-8',遇到不明编码用 chardet 探测,发现坏数据用 errors 参数兜底。 养成这个习惯,就能告别 90% 的编码乱码问题。