Python 中的编码与解码

134 阅读5分钟

本文翻译自我的英文博客,最新修订内容可随时参考:Python中的编码与解码

你真的了解Python中的编码与解码吗?

在计算机中,字符串的存储和网络通信都以**字节序列(byte sequence)**而非Unicode形式进行。Python的编码(encode)和解码(decode)正是用于在字符串(Unicode)和字节序列之间进行转换的核心机制。

一、编码(Encode):从字符串到字节序列

作用:将Unicode字符串转换为指定编码的字节序列,以便存储或传输。
关键要点

  • 必须指定编码类型(如utf-8gbkascii等)。
  • 不同编码对字符的字节表示不同(如"你"在UTF-8中占3字节,在GBK中占2字节)。

示例:UTF-8编码

s = "你好,世界"
encoded_s = s.encode('utf-8')
print(encoded_s)  # 输出: b'\xe4\xbd\xa0\xe5\xa5\xbd\xef\xbc\x8c\xe4\xb8\x96\xe7\x95\x8c'
  • 前缀b表示这是一个字节对象(bytes类型)。
  • 每个中文字符被编码为多个字节(如0xE4 0xBD 0xA0)。

常见编码类型

编码特点适用场景
utf-8可变长编码,支持全球字符,互联网默认编码网页、API数据传输
gbk双字节编码,仅支持中文字符及部分符号中文Windows系统、老旧系统
ascii单字节编码,仅支持英文字母、数字和符号纯英文文本、协议头
utf-16定长编码(2字节或4字节),Unicode直接映射Windows系统内部文本存储

编码错误处理

当字符无法被目标编码表示时,会触发UnicodeEncodeError
解决方案:通过errors参数指定处理方式:

# 忽略无法编码的字符(可能导致数据丢失)  
s.encode('ascii', errors='ignore')  
# 用问号替换(�)  
s.encode('ascii', errors='replace')  
# 用XML实体替换(如你)  
s.encode('ascii', errors='xmlcharrefreplace')  

二、解码(Decode):从字节序列到字符串

作用:将字节序列转换为Unicode字符串,以便程序处理或显示。
关键要点

  • 必须使用与编码时相同的编码类型,否则会导致乱码(如用GBK解码UTF-8字节序列)。
  • 字节序列可能包含无效数据,需处理解码错误。

示例:UTF-8解码

b = b'\xe4\xbd\xa0\xe5\xa5\xbd\xef\xbc\x8c\xe4\xb8\x96\xe7\x95\x8c'
decoded_b = b.decode('utf-8')
print(decoded_b)  # 输出: 你好,世界

解码错误处理

若字节序列包含无效编码(如中途截断的字节),会触发UnicodeDecodeError
解决方案

# 忽略无效字节(可能导致内容缺失)  
b.decode('utf-8', errors='ignore')  
# 用替换字符(�)表示无效字节  
b.decode('utf-8', errors='replace')  
# 保留原始字节(如b'\xe4\xbd' → '\ufffd')  
b.decode('utf-8', errors='surrogateescape')  

三、字符串与字节的本质区别

类型本质常见操作
strUnicode字符串(逻辑字符序列)字符串拼接、正则匹配、格式化
bytes字节序列(物理存储数据)网络传输、文件读写、加密签名

核心转换流程

# 编码流程:Unicode字符串 → 字节序列(存储/传输)  
source_str = "你好"  
encoded_bytes = source_str.encode('utf-8')  # 编码为UTF-8字节  

# 解码流程:字节序列 → Unicode字符串(解析/显示)  
received_bytes = encoded_bytes  
decoded_str = received_bytes.decode('utf-8')  # 解码为原始字符串  

四、常见问题与最佳实践

问题1:中文乱码(编码不匹配)

场景:用GBK编码的字节序列尝试用UTF-8解码。

s = "测试"  
gbk_bytes = s.encode('gbk')       # GBK编码:b'\B2\E2\CA\D4'  
utf8_str = gbk_bytes.decode('utf-8')  # 错误解码 → 输出:测试  

解决方案:确保编码和解码使用相同的字符集。

问题2:字节序与BOM(Byte Order Mark)

场景:UTF-16等定长编码需标识字节序(大端/小端)。

# UTF-16LE(小端序)带BOM  
s.encode('utf-16')  # 输出: b'\xff\xfe\x00\x60\x00\xe4'(\xff\xfe为BOM)  
# 忽略BOM解码  
b.decode('utf-16-le', errors='ignore')  

最佳实践建议

  1. 默认使用UTF-8

    • 除非有特殊需求(如兼容老旧系统),否则优先使用UTF-8编码,避免中文乱码问题。
  2. 明确指定编码

    • 文件读写时显式指定编码(如open('file.txt', 'r', encoding='utf-8')),避免依赖系统默认编码(可能引发跨平台问题)。
  3. 处理编码错误

    • 在数据处理边界(如读取外部文件、网络请求)添加错误处理逻辑,防止程序崩溃。
    try:
        data = bytes_data.decode('utf-8')
    except UnicodeDecodeError:
        data = bytes_data.decode('utf-8', errors='replace')
    
  4. 避免隐式转换

    • 永远不要假设字节序列的编码类型,始终显式指定(如response.content.decode('utf-8')而非str(response.content))。

五、进阶:编码与网络传输

在网络编程中(如HTTP、Socket),数据以字节序列传输,需注意:

  1. HTTP协议

    • 响应头Content-Type字段通常包含编码信息(如text/plain; charset=utf-8)。
    • 使用requests库时,自动根据响应头解码:
      import requests
      response = requests.get('https://example.com')
      print(response.text)  # 自动用UTF-8解码(若响应头正确)
      
  2. Socket编程

    # 发送方(编码)  
    message = "你好".encode('utf-8')  
    socket.send(message)  
    
    # 接收方(解码)  
    data = socket.recv(1024)  
    message = data.decode('utf-8')  
    

总结

编码与解码是Python处理文本数据的基础,核心逻辑可概括为:

  • 编码str → bytes,指定目标编码(如utf-8)。
  • 解码bytes → str,使用与编码一致的编码类型。
  • 关键原则:明确编码类型、处理错误场景、避免隐式转换。

通过理解字符编码的底层机制,可有效解决开发中常见的乱码问题,确保数据在存储、传输和显示过程中的一致性。更多细节可参考Python官方文档:字符串与字节序列