核心概念:从 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 中的字符串:str 和 bytes
理解了上面的概念后,我们来看 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()
str 和 bytes 之间的桥梁就是 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') -> bytesbytes.decode('utf-8') -> unicode_str |
str.encode('utf-8') -> bytesbytes.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"世界" # 正确
实战建议和最佳实践
-
在 Python 3 中,全程使用
str类型。- 你的代码中所有的字符串变量、函数参数、返回值都应该是
str。 - 只在输入/输出环节才考虑
bytes。
- 你的代码中所有的字符串变量、函数参数、返回值都应该是
-
统一使用 UTF-8 编码。
- 在读写文件时,明确指定
encoding='utf-8'。 - 在网络通信中,优先使用 UTF-8。
- 这可以避免绝大多数编码问题。
- 在读写文件时,明确指定
-
文件读写:
- 读取文本文件: 使用
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'> - 读取文本文件: 使用
-
处理外部数据(如 API、数据库):
- 当从 API 或数据库接收到数据时,如果它返回的是 JSON,Python 的
json模块会自动处理好解码,得到的就是str。 - 如果是原始的二进制数据,你需要知道它的编码格式,然后使用
.decode('正确的编码')将其转为str。
- 当从 API 或数据库接收到数据时,如果它返回的是 JSON,Python 的
-
处理命令行参数:
- 从
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,你的中文处理之路就会平坦很多。
