杰瑞科技汇

Python中如何处理中文Unicode编码?

核心概念:从 ASCII 到 Unicode

ASCII (American Standard Code for Information Interchange)

在计算机早期,为了解决英文字符的编码问题,诞生了 ASCII 编码,它用 7 个比特(bit)来表示一个字符,总共可以表示 128 个字符,涵盖了英文字母、数字和一些常用符号。

问题: ASCII 无法表示中文、日文、韩文等非拉丁字符,一个字节(8 bit)最多只能表示 256 个字符,对于成千上万的汉字来说是远远不够的。

Unicode

为了解决全球所有语言的字符编码问题,Unicode 应运而生,它是一个字符集,为世界上几乎所有的字符都分配了一个唯一的数字,称为“码点”(Code Point)。

  • 码点: 一个 Unicode 字符的唯一标识,通常用 U+ 开头,后面跟一个 16 进制的数字。
    • A 的码点是 U+0041
    • 的码点是 U+4E2D
    • 的码点是 U+1F602

重要: Unicode 本身只规定了“字符”和“数字”之间的映射关系,但它没有规定这个数字应该如何存储在计算机中,这就引出了 Unicode 的实现方式,也就是我们常说的 UTF-8, UTF-16, UTF-32

UTF-8 (Unicode Transformation Format - 8-bit)

UTF-8 是目前最流行、最通用的 Unicode 实现,它是一种变长编码

  • 对于 ASCII 字符(0-127),UTF-8 使用 1 个字节存储,并且与 ASCII 完全兼容。
  • 对于非 ASCII 字符(如中文),UTF-8 使用 2 到 4 个字节存储。

优点:

  • 兼容性好: 纯英文文本的体积和 ASCII 一样。
  • 节省空间: 对于大部分中文,使用 3 个字节,比 UTF-16(4 个字节)更节省空间。
  • 自同步: 即使在传输过程中丢失了部分字节,也很容易重新找到下一个字符的开始位置。

字的 UTF-8 编码: 的码点是 U+4E2D,在 UTF-8 中被编码为三个字节:E4 B8 AD


Python 3 中的字符串:strbytes

理解了上面的概念后,我们来看 Python 3 是如何处理字符串的,Python 3 在设计上对 Unicode 的支持非常友好,明确区分了两种类型:

str 类型:Unicode 字符串

这是 Python 3 中表示文本的标准字符串类型。

  • 本质: 它是一个抽象的字符序列,你存储的是字符本身,而不是它们的字节表示。
  • 内存中: Python 内部会用一种高效的方式(通常是 UTF-16 或 UTF-32,取决于你的系统和 Python 构建)来存储这些 Unicode 字符,但这对你来说是透明的。
  • 不可变: 和 Python 2 中的 unicode 一样,str 对象是不可变的。
# s 是一个 str 对象,它代表的是“字符”序列
s = "你好,世界!"
# 查看类型
print(type(s))  # <class 'str'>
# 查看单个字符的 Unicode 码点
print(ord('中'))  # 输出: 20013 (十进制)
print(hex(ord('中'))) # 输出: 0x4e2d (十六进制)
# 可以直接混合使用中文和英文
s_mixed = "Hello, 世界!"
print(s_mixed) # 输出: Hello, 世界!

bytes 类型:字节序列

bytes 类型表示的是原始的字节,而不是字符,它常用于网络传输、文件读写等二进制数据操作。

  • 本质: 它是一个 0-255 范围内的整数的序列。
  • 每个元素: 是一个字节(8 bits)。
  • 不可变: 有一个可变的版本叫做 bytearray
# b 是一个 bytes 对象,它代表的是“字节”序列
# 前缀 b 表示这是一个 bytes 字面量
b = b'hello'
# 查看类型
print(type(b))  # <class 'bytes'>
# 查看每个字节
print(list(b))  # 输出: [104, 101, 108, 108, 111]

关键区别: "你好" 是一个 str,而 b"你好" 是一个 bytes,后者在 Python 中是不合法的,因为 bytes 字面量只能包含 ASCII 字符。


核心操作:encode()decode()

strbytes 之间的桥梁就是 encode()decode() 方法。

encode():将 str 编码为 bytes

当你需要将字符串存储到文件、通过网络发送或进行其他二进制操作时,你需要将它从 str 转换为 bytes,这个过程就是编码

# 定义一个 Unicode 字符串
s = "你好,世界!"
# 使用 encode() 方法将其编码为 UTF-8 格式的 bytes
# 默认编码就是 'utf-8'
b_utf8 = s.encode('utf-8')
print(f"原始字符串: {s}")
print(f"类型: {type(s)}")
print(f"编码后的字节: {b_utf8}")
print(f"类型: {type(b_utf8)}")
# 输出:
# 原始字符串: 你好,世界!
# 类型: <class 'str'>
# 编码后的字节: b'\xe4\xbd\xa0\xe5\xa5\xbd\xef\xbc\x8c\xe4\xb8\x96\xe7\x95\x8c\xef\xbc\x81'
# 类型: <class 'bytes'>
# 也可以编码为其他格式,如 GBK (一种中文编码)
b_gbk = s.encode('gbk')
print(f"GBK 编码后的字节: {b_gbk}")
# 输出:
# GBK 编码后的字节: b'\xc4\xe3\xba\xc3\xa3\xac\xca\xa1\xca\xa1\xa3\x81'

decode():将 bytes 解码为 str

当你从文件或网络接收到二进制数据(bytes)时,你需要将它转换回人类可读的字符串(str),这个过程就是解码

# 假设我们从网络收到了一个 UTF-8 编码的字节流
b_received = b'\xe4\xbd\xa0\xe5\xa5\xbd'
# 使用 decode() 方法将其解码为 str
s_decoded = b_received.decode('utf-8')
print(f"接收到的字节: {b_received}")
print(f"类型: {type(b_received)}")
print(f"解码后的字符串: {s_decoded}")
print(f"类型: {type(s_decoded)}")
# 输出:
# 接收到的字节: b'\xe4\xbd\xa0\xe5\xa5\xbd'
# 类型: <class 'bytes'>
# 解码后的字符串: 你好
# 类型: <class 'str'>

编码和解码必须使用相同的格式! 如果用错误的格式解码,就会导致 UnicodeDecodeError

# 错误示例:用 GBK 解码 UTF-8 编码的数据
try:
    b_utf8.decode('gbk')
except UnicodeDecodeError as e:
    print(f"解码失败!错误信息: {e}")
# 输出:
# 解码失败!错误信息: 'gbk' codec can't decode byte 0xe4 in position 0: illegal multibyte sequence

Python 2 vs Python 3 的关键区别

这是一个常见的“坑”,很多从 Python 2 迁移到 Python 3 的开发者会遇到。

特性 Python 2 Python 3
字符串类型 str: 字节串,默认编码是 ASCII。
unicode: Unicode 字符串。
str: Unicode 字符串。
bytes: 字节串。
默认字符串 str (字节串) str (Unicode 字符串)
编码/解码 unicode_str.encode('utf-8') -> bytes
bytes.decode('utf-8') -> unicode_str
str.encode('utf-8') -> bytes
bytes.decode('utf-8') -> str
中文处理 必须显式地使用 u"你好" 创建 unicode 字符串,否则 "你好"str,可能导致乱码。 直接使用 "你好" str (Unicode),处理起来非常直观。

Python 2 中的常见问题:

# Python 2
# s 是一个 str (字节串),假设文件是用 UTF-8 编码的
s = "你好" # 这里的 "你好" 实际上是文件读入的字节
# 直接拼接一个 unicode 字符串
try:
    result = s + u"世界" # TypeError: cannot concatenate 'str' and 'unicode' objects
except TypeError as e:
    print(e)
# 正确做法是先解码
s_unicode = s.decode('utf-8')
result = s_unicode + u"世界" # 正确

实战建议和最佳实践

  1. 在 Python 3 中,全程使用 str 类型。

    • 你的代码中所有的字符串变量、函数参数、返回值都应该是 str
    • 只在输入/输出环节才考虑 bytes
  2. 统一使用 UTF-8 编码。

    • 在读写文件时,明确指定 encoding='utf-8'
    • 在网络通信中,优先使用 UTF-8。
    • 这可以避免绝大多数编码问题。
  3. 文件读写:

    • 读取文本文件: 使用 open() 并指定 encoding='utf-8',它会自动将文件中的字节解码为 str 返回给你。
    • 写入文本文件: 使用 open() 并指定 encoding='utf-8',你传入的 str 会自动被编码为字节写入文件。
    # 写入文件
    with open('output.txt', 'w', encoding='utf-8') as f:
        f.write("你好,Python!")
    # 读取文件
    with open('output.txt', 'r', encoding='utf-8') as f:
        content = f.read()
        print(content)  # 输出: 你好,Python!
        print(type(content)) # 输出: <class 'str'>
  4. 处理外部数据(如 API、数据库):

    • 当从 API 或数据库接收到数据时,如果它返回的是 JSON,Python 的 json 模块会自动处理好解码,得到的就是 str
    • 如果是原始的二进制数据,你需要知道它的编码格式,然后使用 .decode('正确的编码') 将其转为 str
  5. 处理命令行参数:

    • sys.argv 获取的命令行参数是 str 类型,Python 已经帮你处理好了编码问题。
概念 描述 Python 3 对应
字符 抽象的文字,如 'A', '中' str
码点 字符的唯一数字标识 ord() 获取,chr() 转换
编码 将字符(码点)转换为字节序列的过程 str.encode()
解码 将字节序列转换回字符的过程 bytes.decode()
字节 计算机存储和传输的 0-255 的整数 bytes
UTF-8 最流行的 Unicode 实现,变长编码 默认推荐使用的编码格式

记住这个核心流程:你的程序内部使用 str (Unicode 字符串) 进行逻辑处理;在与外部世界(文件、网络)交互时,使用 encode()str 转为 bytes 发送出去,使用 decode() 将接收到的 bytes 转为 str 进行处理。 坚持使用 UTF-8,你的中文处理之路就会平坦很多。

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