从 ASCII 到 Unicode
在深入 Python 之前,我们先理解一下为什么需要 Unicode。
-
ASCII 的局限性:
- ASCII (American Standard Code for Information Interchange) 编码用 1 个字节 (8 bits) 来表示字符。
- 它只能表示 128 个字符,主要包括英文字母(大小写)、数字和一些特殊符号,对于英语世界来说足够了,但对于中文、日文、俄文、阿拉伯文等语言,ASCII 就完全无能为力了。
-
Unicode 的诞生:
- 为了解决全球语言的编码问题,Unicode 应运而生,它的目标是为世界上每一个字符分配一个唯一的数字,称为码点。
A的码点是U+0041中的码点是U+4E2D- (欧元符号) 的码点是
U+20AC
- Unicode 本身只定义了字符和码点之间的映射关系,但它并没有规定如何将这些码点存储在计算机中,这就引出了UTF-8、UTF-16 等具体的编码实现。
Python 3 中的 Unicode 字符串
Python 3 对 Unicode 的处理非常友好,可以说是其设计上的一个巨大进步。
所有字符串都是 Unicode 字符串
在 Python 3 中,当你创建一个字符串时,它默认就是一个 Unicode 字符串。
# 在 Python 3 中,下面这两个字符串是完全等价的 s1 = "Hello, 世界!" s2 = '你好,世界!' # 查看类型 print(type(s1)) # <class 'str'> print(type(s2)) # <class 'str'> # str 类型就是 Unicode 字符串类型
这与 Python 2 形成了鲜明对比,在 Python 2 中,str 是字节串,而 unicode 才是真正的 Unicode 字符串,这是 Python 2 和 Python 3 在处理文本上最根本的区别之一。
码点和字符
在 Python 中,你可以使用 ord() 函数获取一个字符的 Unicode 码点(整数),使用 chr() 函数根据码点获取对应的字符。
# 获取字符 'A' 的码点
code_point_A = ord('A')
print(f"'A' 的码点是: {code_point_A}") # 'A' 的码点是: 65
# 获取字符 '中' 的码点
code_point_zh = ord('中')
print(f"'中' 的码点是: {code_point_zh}") # '中' 的码点是: 20013
# 根据码点 '65' 获取字符
char_A = chr(65)
print(f"码点 65 对应的字符是: {char_A}") # 码点 65 对应的字符是: A
# 根据码点 '20013' 获取字符
char_zh = chr(20013)
print(f"码点 20013 对应的字符是: {char_zh}") # 码点 20013 对应的字符是: 中
字符串的表示形式
你可能会在代码或输出中看到 \u 或 \U 开头的表示法,这是 Python 对 Unicode 字符的转义表示。
\u后面跟 4 个十六进制数字,表示一个基本多文种平面 的字符。\U后面跟 8 个十六进制数字,可以表示更广范围内的字符。
# 使用 \u 转义 s_with_escape = "Hello, \u4E2D\u6587" # \u4E2D 是 '中', \u6587 是 '文' print(s_with_escape) # 输出: Hello, 中文 # 也可以使用原始字符串 r"..." 来避免反斜杠被转义 s_raw = r"Hello, \u4E2D\u6587" print(s_raw) # 输出: Hello, \u4E2D\u6587
字符串与字节串 的区别
这是理解 Python Unicode 的关键。字符串是抽象的字符序列,而字节串是原始的字节序列。
str(字符串):人类可读的字符的抽象表示。"你好"。bytes(字节串):计算机存储和传输的 0 和 1 的序列。b'\xe4\xbd\xa0\xe5\xa5\xbd'。
要将字符串和字节串进行转换,你需要指定一种编码方式,最常用的是 UTF-8。
编码:str -> bytes
使用字符串的 .encode() 方法。
my_string = "你好,Python!"
# 将字符串编码为 UTF-8 字节串
# 注意前面的 b'' 表示这是一个字节串
utf8_bytes = my_string.encode('utf-8')
print(f"原始字符串: {my_string}")
print(f"UTF-8 编码后的字节串: {utf8_bytes}")
# 输出:
# 原始字符串: 你好,Python!
# UTF-8 编码后的字节串: b'\xe4\xbd\xa0\xe5\xa5\xbd\xef\xbc\x8cPython\xef\xbc\x81'
# 也可以使用其他编码,如 gbk (中文常用)
gbk_bytes = my_string.encode('gbk')
print(f"GBK 编码后的字节串: {gbk_bytes}")
# 输出:
# GBK 编码后的字节串: b'\xc4\xe3\xba\xc3\xef\xbc\x8cPython\xa3\xa1'
解码:bytes -> str
使用字节串的 .decode() 方法。
# 假设我们有一个 UTF-8 编码的字节串
received_bytes = b'\xe4\xbd\xa0\xe5\xa5\xbd'
# 将其解码为字符串
decoded_string = received_bytes.decode('utf-8')
print(f"解码后的字符串: {decoded_string}")
# 输出:
# 解码后的字符串: 你好
# 如果用错误的编码解码,会抛出 UnicodeDecodeError
try:
received_bytes.decode('gbk') # 尝试用 GBK 解码 UTF-8 字节
except UnicodeDecodeError as e:
print(f"解码失败: {e}")
# 输出:
# 解码失败: 'gbk' codec can't decode byte 0xa5 in position 1: illegal multibyte sequence
常见操作
遍历字符串
Python 的字符串遍历是基于字符的,而不是字节,这非常直观。
s = "Hello 世界"
for char in s:
print(f"字符: {char}, 码点: {ord(char)}")
输出:
字符: H, 码点: 72
字符: e, 码点: 101
字符: l, 码点: 108
字符: l, 码点: 108
字符: o, 码点: 111
字符: , 码点: 32
字符: 世, 码点: 19990
字符: 界, 码点: 30028
获取字符串长度
len() 函数返回的是字符串中字符的数量,而不是字节数。
s = "Hello 世界"
print(f"字符串 '{s}' 的字符长度是: {len(s)}") # 输出: 7
处理文件读写
在处理文件时,必须明确指定编码,以避免乱码。
# 写入文件,确保使用 utf-8 编码
content = "这是一个包含中文的文件。"
with open('my_file.txt', 'w', encoding='utf-8') as f:
f.write(content)
# 读取文件,也必须使用相同的编码
with open('my_file.txt', 'r', encoding='utf-8') as f:
read_content = f.read()
print(f"从文件读取的内容: {read_content}")
# 输出:
# 从文件读取的内容: 这是一个包含中文的文件。
总结与最佳实践
- 在 Python 3 中,
strUnicode,这是你的主要工作类型。 bytes是用于存储和网络传输的原始数据,当你需要将字符串写入文件、发送到网络或进行某些底层操作时,才需要将其编码为bytes。- 始终明确指定编码,在文件操作、网络请求等所有涉及编码转换的地方,都明确使用
encoding='utf-8',UTF-8 是目前事实上的标准,因为它高效且兼容 ASCII。 - 处理用户输入和文件内容时,优先解码为
str,在程序内部,统一使用str类型进行所有的逻辑处理、切片、拼接等操作,只有在需要持久化或传输时,才将其编码为bytes。 - 遇到
UnicodeEncodeError或UnicodeDecodeError,几乎可以肯定是编码和解码时使用的编码格式不一致,检查你的代码,确保在转换的两端使用了相同的编码(通常是 UTF-8)。
掌握了 Python 的 Unicode 字符串处理,你就能更自信地编写健壮、国际化的应用程序。
