'乱码'是每个 Python 开发者,尤其是处理中文、日文等非 ASCII 字符时,都会遇到的'噩梦'。明明代码逻辑正确,文件也存在,但打印出来或保存的文件却是一堆莫名其妙的符号(如 éÂ\x87Â\x91éÂ\x9eÂ\x93)。
这篇文章将带你彻底理解乱码产生的根本原因,并提供一套行之有效的解决方案和最佳实践。
一、乱码的本质:编码与解码的'鸡同鸭讲'
要理解乱码,首先必须明白两个核心概念:字符集(Charset) 和 字符编码(Character Encoding)。
- 字符集(Charset):是一个系统支持的所有抽象字符的集合。比如:
- ASCII:包含 128 个字符(英文字母、数字、符号),用 1 个字节(8 位)表示。
- GBK/GB2312:中国国家标准,包含汉字、符号等,用 1 或 2 个字节表示。
- Unicode:一个超级字符集,包含了世界上几乎所有语言的字符。它本身不是编码,而是编码的基础。
- 字符编码(Encoding):是将字符集中的字符映射为二进制数据(字节)的规则。Unicode 字符集有多种实现编码:
- UTF-8:变长编码(1-4 字节),兼容 ASCII,是互联网的事实标准。
- UTF-16:固定 2 或 4 字节。
- UTF-32:固定 4 字节。
乱码产生的根本原因:编码和解码时使用了不同的规则。
(想象一个流程图:字符 -> [编码] -> 字节 -> [解码] -> 字符。如果编码和解码的规则不一致,就会得到错误的字符)
举个例子:
汉字'金'的 Unicode 码点是 U+91D1。
- 用 UTF-8 编码后,字节序列是:
0xE9 0x87 0x91 - 用 GBK 编码后,字节序列是:
0xBD 0xF0
如果你用 UTF-8 编码了'金',得到 0xE9 0x87 0x91,但却错误地用 GBK 去解码它,GBK 会认为 0xE9 是一个汉字的第一个字节,并尝试寻找第二个字节,最终组合成一个完全不同的、甚至无效的字符,这就是乱码。
二、Python 中的乱码重灾区与解决方案
Python 3 在内存中统一使用 Unicode(准确说是 UCS-4/UTF-32 的子集)来表示字符串,这大大减少了内存中的乱码问题。乱码主要发生在'输入/输出'环节,即字节流(bytes)和字符串(str)转换的边界。
场景 1:文件读写(最常见!)
错误示范:
# 写入文件时未指定编码(使用系统默认编码,Windows 下通常是 GBK)
with open('test.txt', 'w') as f:
f.write('金')
# 内存中的 Unicode '金' 被用系统编码(如 GBK)转换为字节写入
# 读取文件时也未指定编码
with open(, ) f:
content = f.read()

