杰瑞科技汇

python unicode 乱码

编码与解码

要理解乱码,首先要明白两个核心概念:编码解码

python unicode 乱码-图1
(图片来源网络,侵删)

想象一下,计算机内部只认识 01,而我们要处理的文字(比如中文 "你好")在计算机里不是以 "你好" 的形式存储的,而是被转换成一串特定的二进制数字,这个“转换过程”就是编码,反过来,计算机要把这串二进制数字还原成我们能看懂的文字,这个过程就是解码

乱码的本质就是:用 A 编码规则编码过的数据,被用 B 解码规则解码了。 这就像你用中文写了一封信,却用了一个只懂英文的密码本去读,结果自然是一堆无意义的字符。


Python 2 vs Python 3 的关键区别

这个问题在 Python 2 和 Python 3 中表现完全不同,这也是很多初学者混淆的地方。

Python 2 的“坑”:str vs unicode

在 Python 2 中,字符串类型有两种:

  1. str:字节串,它直接存储原始的字节,没有指定编码,你可以说它是一个“未经加工的”数据块。
  2. unicode:真正的 Unicode 字符串,它内部使用统一的编码(通常是 UTF-16 或 UTF-32)来存储字符,与具体的编码方案无关。

乱码场景: 当你有一个 str 类型的变量(比如从文件读取或网络请求得到的数据),但你直接把它当成 unicode 来处理,或者在打印时,Python 会尝试使用默认的编码(通常是 ASCII)去“解码”它,如果原始数据是 UTF-8 编码的中文,用 ASCII 去解码,必然会产生乱码。

Python 2 的解决方案: 遵循 "Unicode Sandwich"(三明治原则):

  1. 入口:在程序接收外部数据(文件、网络、用户输入)时,立即解码unicode
  2. 中间:在程序内部,所有处理都使用 unicode 类型。
  3. 出口:在程序输出数据(写入文件、发送网络、打印到屏幕)时,立即编码成目标编码(如 UTF-8)。

示例代码 (Python 2):

# 假设我们从文件中读取了一行UTF-8编码的中文
# 在Python 2中,file.read()返回的是str类型(字节串)
# u = "你好,世界"  # 这是unicode字面量
# encoded_str = u.encode('utf-8') # 模拟从文件读取到的UTF-8字节串
encoded_str = "\xe4\xbd\xa0\xe5\xa5\xbd\xef\xbc\x8c\xe4\xb8\x96\xe7\x95\x8c" # UTF-8编码的字节
# 错误示范:直接打印str,Python会用默认ASCII解码,产生乱码
print encoded_str # 输出: ä½ å¥½ï¼Œä¸ç•
# 正确示范:在处理前,先解码成unicode
unicode_str = encoded_str.decode('utf-8')
print unicode_str # 输出: 你好,世界
# 程序内部处理...
# ...
# 输出前,再编码成想要的格式,比如写入文件
output_str = unicode_str.encode('utf-8')
# write_to_file(output_str)

Python 3 的“进化”:str vs bytes

Python 3 从根本上解决了这个问题,让类型更清晰:

  1. str:现在就是 Unicode 字符串,它的行为和 Python 2 的 unicode 一样,是抽象的字符序列,不再关心底层编码。
  2. bytes:字节串,它的行为和 Python 2 的 str 一样,是原始的字节序列。

乱码场景: 在 Python 3 中,乱码通常发生在 strbytes 之间的转换上。

  • 当你需要将 str 写入文件或通过网络发送时,必须先将其编码bytes
  • 当你从文件或网络读取数据得到 bytes 时,必须先将其解码str 才能进行文本处理。

Python 3 的解决方案: 同样遵循 "Unicode Sandwich" 原则,但类型更明确:

  1. 入口:将外部输入的 bytes 解码str
  2. 中间:在程序内部,所有文本处理都使用 str
  3. 出口:将程序内部的 str 编码bytes 再输出。

示例代码 (Python 3):

# 假设我们从文件中读取了一行UTF-8编码的中文
# 在Python 3中,file.read()返回的是bytes类型
# s = "你好,世界" # 这是str类型(Unicode)
# encoded_bytes = s.encode('utf-8') # 模拟从文件读取到的UTF-8字节串
encoded_bytes = b'\xe4\xbd\xa0\xe5\xa5\xbd\xef\xbc\x8c\xe4\xb8\x96\xe7\x95\x8c' # UTF-8编码的字节串 (bytes)
# 错误示范:直接打印bytes,Python会把它当成字节数据的repr显示
print(encoded_bytes) # 输出: b'\xe4\xbd\xa0\xe5\xa5\xbd\xef\xbc\x8c\xe4\xb8\x96\xe7\x95\x8c'
# 正确示范:在处理前,先解码成str
unicode_str = encoded_bytes.decode('utf-8')
print(unicode_str) # 输出: 你好,世界
# 程序内部处理...
# ...
# 输出前,再编码成bytes,比如写入文件
output_bytes = unicode_str.encode('utf-8')
# write_to_file(output_bytes)

常见乱码场景及解决方案(以 Python 3 为例)

场景 1:打印到终端时乱码

原因:终端的字符编码环境可能不是 UTF-8,在 Windows 的默认 CMD 终端中,编码是 gbk

问题代码:

s = "你好,世界"
# 如果你的终端是GBK编码,这行代码会报错:UnicodeEncodeError: 'gbk' codec can't encode character...
print(s)

解决方案

  1. 推荐方法:确保你的终端支持 UTF-8,在现代的 Windows Terminal、macOS Terminal 或 Linux 终端中,这通常是默认设置。
  2. 临时解决:如果无法改变终端,可以在打印时指定编码。
    import sys
    s = "你好,世界"
    # 将str编码成gbk,然后告诉标准输出用gbk处理
    print(s.encode(sys.stdout.encoding, errors='replace').decode(sys.stdout.encoding))
    # errors='replace' 会在无法编码的字符处插入 '?'

场景 2:读写文件时乱码

原因:没有在打开文件时指定正确的编码。

问题代码(读取):

# 文件 content.txt 是用UTF-8编码保存的 "你好,世界"
# 错误的读取方式
with open('content.txt', 'r') as f:
    content = f.read() # 默认使用系统编码,可能是ASCII或GBK,导致乱码
    print(content) # 可能输出乱码或报错

解决方案(读写): 始终在打开文件时明确指定 encoding 参数!

# 正确的读取方式
with open('content.txt', 'r', encoding='utf-8') as f:
    content = f.read() # content是str类型
    print(content) # 输出: 你好,世界
# 正确的写入方式
s = "你好,世界"
with open('output.txt', 'w', encoding='utf-8') as f:
    f.write(s) # Python会自动将str编码成UTF-8写入

场景 3:网络请求时乱码

原因requests 等库返回的响应体(response.text)在解码时可能使用了错误的编码。

问题代码:

import requests
response = requests.get('https://www.example.com/some-chinese-page')
# response.text 可能会乱码,因为requests库有时会猜错编码
print(response.text)

解决方案

  1. 使用 response.content 手动解码(最可靠) response.content 返回的是原始的 bytes 数据,你可以自己控制解码过程。

    import requests
    response = requests.get('https://www.example.com/some-chinese-page')
    # 1. 获取原始字节
    content_bytes = response.content
    # 2. 手动解码,可以从响应头中找到编码,如 response.encoding
    #    如果响应头没有,可以尝试 'utf-8'
    content_str = content_bytes.decode('utf-8', errors='replace')
    print(content_str)
  2. 设置正确的 response.encoding

    import requests
    response = requests.get('https://www.example.com/some-chinese-page')
    # 如果你知道或者能从响应头中看到正确的编码,直接设置
    response.encoding = 'utf-8' # 'gbk', 'big5' 等
    print(response.text) # 现在response.text会使用你设置的编码来解码

最佳实践总结

  1. 明确你的代码运行环境:你用的是 Python 2 还是 Python 3?这决定了你的基本策略。
  2. 拥抱 Python 3:Python 3 的 strbytes 分离使得处理文本和二进制数据更清晰,强烈建议使用 Python 3。
  3. 遵循 "Unicode Sandwich" 原则
    • 输入 -> 解码成 str
    • 处理 -> 使用 str
    • 输出 -> 编码成 bytes
  4. 永远不要依赖默认编码:Python 2 的 str 和 Python 3 的 open()print() 等都有默认编码,这个编码可能因操作系统、环境而异。显式指定编码是唯一可靠的方法
  5. 处理文件和网络 I/O 时,务必指定 encoding='utf-8':UTF-8 是目前事实上的标准编码,兼容性最好。
  6. 处理不确定的编码时
    • 优先使用 bytes.decode('utf-8', errors='replace')bytes.decode('utf-8', errors='ignore')
    • errors='replace' 会将无法解码的字符替换成一个占位符(通常是 )。
    • errors='ignore' 会直接跳过无法解码的字符。
  7. 使用 chardet 库猜测编码:如果你完全不知道一个 bytes 对象的编码,可以使用 chardet 库来猜测。
    import chardet
    unknown_bytes = b'\xe4\xbd\xa0\xe5\xa5\xbd'
    result = chardet.detect(unknown_bytes)
    encoding = result['encoding'] # 'utf-8'
    confidence = result['confidence'] # 0.99...
    print(unknown_bytes.decode(encoding))

掌握了这些核心概念和最佳实践,你就能应对 99% 的 Python Unicode 乱码问题了。

分享:
扫描分享到社交APP
上一篇
下一篇