这是一个非常重要的话题,因为在 Python 2 和 Python 3 中,处理方式有根本性的不同,我会分别进行说明,并重点介绍现代 Python(Python 3)的最佳实践。

核心概念:Unicode vs. 编码
在开始之前,必须先理解两个关键概念:
-
Unicode (字符集):
- 可以把它想象成一个巨大的字符库,包含了世界上几乎所有语言的字符(如 'A', '中', '€', '😂' 等)。
- 它为每个字符分配了一个唯一的数字,这个数字被称为 码点。'A' 的码点是
U+0041,'中' 的码点是U+4E2D。 - Unicode 本身只定义了字符和码点的对应关系,它不是一种编码格式。
-
编码 (Encoding):
- 可以把它想象成一种“打包”规则,用于将 Unicode 码点转换成计算机可以存储和传输的字节序列。
- 同一个 Unicode 字符,可以用不同的编码规则打包成不同的字节。
- 常见的编码有:
- UTF-8:目前最主流的编码,它是一种变长编码,英文字符(如 'A')占用 1 个字节,中文字符通常占用 3 个字节,它对 ASCII 兼容,并且容错性好。
- UTF-16:变长编码,英文字符通常占用 2 个字节,中文字符通常占用 3 或 4 个字节,常用于 Windows 和 Java 内部。
- GBK / GB2312:中国的国标编码,主要支持中文字符,在处理旧的中文系统或文件时可能会遇到。
- ASCII:最古老的编码,仅支持英文字符,每个字符占 1 个字节。
一个简单的比喻:

- Unicode 是一本包含所有汉字的字典,每个汉字都有一个唯一的页码和位置(码点)。
- 编码(如 UTF-8) 是一种电报码,当你想把一个汉字(如“中”)通过电报发送出去时,你需要查字典找到它的“电报码”(字节序列),对方收到后,再用同样的字典把“电报码”翻译回汉字(解码)。
Python 3 中的 Unicode (推荐)
Python 3 从设计之初就将 Unicode 作为字符串的内部表示,这使得处理多语言文本变得非常简单和直观。
字符串的本质:str vs. bytes
在 Python 3 中,有两种基本的数据类型来处理文本和二进制数据:
str(字符串):用于表示文本,它的内部存储就是 Unicode 码点,你直接在代码里写的'hello'或'你好'都是一个str对象,它不关心字节,只关心字符。bytes(字节序列):用于表示二进制数据,它是一堆原始的字节(0-255),没有任何编码含义,当你需要将数据写入文件、网络传输时,通常需要将str编码成bytes。
设置和指定编码:encode() 与 decode()
这是 Python 3 中处理编码的核心操作。
str.encode(encoding='utf-8'):将字符串(str)编码成字节序列(bytes)。bytes.decode(encoding='utf-8'):将字节序列(bytes)解码成字符串(str)。
示例:

# 1. 定义一个 Unicode 字符串
# Python 3 的 str 默认就是 Unicode
my_string = "你好,世界!Hello, World! 😊"
print(f"原始字符串类型: {type(my_string)}")
print(f"原始字符串内容: {my_string}")
# 2. 将字符串编码成 UTF-8 格式的字节序列
# 这是写入文件或网络传输前的标准步骤
utf8_bytes = my_string.encode('utf-8')
print(f"\n编码后的字节序列: {utf8_bytes}")
print(f"编码后的类型: {type(utf8_bytes)}")
# 输出类似: b'\xe4\xbd\xa0\xe5\xa5\xbd\xef\xbc\x8c\xe4\xb8\x96\xe7\x95\x8c\xef\xbc\x81Hello, World! \xf0\x9f\x98\x8a'
# 每个开头的 \x 代表一个十六进制字节
# 3. 将字节序列解码回字符串
# 这是读取文件或网络数据后的标准步骤
decoded_string = utf8_bytes.decode('utf-8')
print(f"\n解码后的字符串: {decoded_string}")
print(f"解码后的类型: {type(decoded_string)}")
print(f"解码后是否与原始字符串一致: {my_string == decoded_string}")
文件操作中的编码 (open() 函数)
在读写文件时,必须明确指定编码,否则 Python 会使用系统的默认编码,这在不同操作系统上可能导致乱码。
最佳实践:始终使用 encoding='utf-8'
# 写入文件 (使用 UTF-8 编码)
content_to_write = "这是一段使用 UTF-8 编码的中文文本。"
with open('my_file.txt', 'w', encoding='utf-8') as f:
f.write(content_to_write)
print("文件已使用 UTF-8 编码写入。")
# 读取文件 (同样使用 UTF-8 编码)
with open('my_file.txt', 'r', encoding='utf-8') as f:
content_read = f.read()
print(f"从文件读取的内容: {content_read}")
print(f"读取内容的类型: {type(content_read)}")
常见错误与处理:
如果读取一个用 GBK 编码保存的文件,但你用 utf-8 去读,会报错 UnicodeDecodeError。
# 假设 'gbk_file.txt' 是用 GBK 编码保存的
try:
with open('gbk_file.txt', 'r', encoding='utf-8') as f:
f.read()
except UnicodeDecodeError:
print("错误:解码失败!文件可能不是 UTF-8 编码。")
# 正确做法是知道或猜测出正确的编码
try:
with open('gbk_file.txt', 'r', encoding='gbk') as f:
content = f.read()
print(f"使用 GBK 编码成功读取: {content}")
except FileNotFoundError:
print("文件 gbk_file.txt 不存在,这是一个示例。")
Python 2 中的 Unicode (已过时,仅作了解)
Python 2 的处理方式比较混乱,是常见的乱码来源。
str:字节串,默认编码是 ASCII。unicode:真正的 Unicode 字符串。
你需要手动在 str 和 unicode 之间转换。
# Python 2 代码示例
# -*- coding: utf-8 -*- # 文件头声明,告诉 Python 解释器这个文件本身是 UTF-8 编码的
# 定义一个字节串
my_str = "你好" # 在 Python 2 中,这只是一个字节串,如果你的源文件编码是 UTF-8,它存储的是 '你' 和 '好' 的 UTF-8 编码字节
print type(my_str) # <type 'str'>
# 将字节串解码成 Unicode
my_unicode = my_str.decode('utf-8')
print type(my_unicode) # <type 'unicode'>
# 将 Unicode 编码成字节串
another_str = my_unicode.encode('utf-8')
print type(another_str) # <type 'str'>
# 如果不进行转换,直接拼接 str 和 unicode,会报错
# my_str + my_unicode # 会抛出 TypeError: cannot concatenate 'str' and 'unicode' objects
如果你还在维护 Python 2 项目,请务必仔细处理 str 和 unicode 的转换,对于新项目,请直接使用 Python 3。
总结与最佳实践
- 使用 Python 3:它内置了对 Unicode 的完美支持,能避免绝大多数编码问题。
- 牢记
str和bytes的区别:str是文本,bytes是二进制数据。- 代码中处理文本、变量、逻辑时,始终使用
str。 - 读写文件、网络收发数据时,
str需要先用.encode()变成bytes,收到的bytes需要用.decode()变成str。
- 统一使用 UTF-8 编码:
- 文件操作:始终在
open()函数中指定encoding='utf-8'。 - Web 开发:确保你的 HTTP 响应头包含
Content-Type: text/html; charset=utf-8。 - 数据库:连接数据库时指定使用 UTF-8 编码。
- 文件操作:始终在
- 不要依赖系统默认编码:显式地、明确地指定
utf-8是最安全、最可移植的做法。 - 处理异常:当你不确定来源文件的编码时,准备好处理
UnicodeDecodeError异常。
遵循这些原则,你就可以在 Python 中游刃有余地处理任何文本和编码问题了。
