Unicode 字符串
Python 2 的字符串系统最大的设计缺陷,是同时存在两种字符串类型:str(字节串)和 unicode(Unicode 字符序列)。这种分裂导致无数编码错误,也是 Python 3 彻底重构字符串系统的核心原因。
为什么需要 unicode 类型
计算机最初只处理 ASCII 字符(128 个),用一个字节表示。但随着互联网全球化,中文、日文、阿拉伯文等文字需要被表示。Unicode 标准应运而生:它为世界上几乎所有字符分配了唯一的数字编号(码点,code point),范围从 U+0000 到 U+10FFFF。
unicode 类型在 Python 2 中存储的就是这些码点序列,而非字节。它屏蔽了底层编码细节,让程序员可以像处理 ASCII 一样处理任何文字:
u = u"你好,世界"
print type(u) # <type 'unicode'>
print len(u) # 6,6 个字符
print u[0] # u'你'
对比 str 的混乱行为:
s = "你好,世界" # 如果文件编码是 UTF-8,这实际上是 18 个字节
print type(s) # <type 'str'>
print len(s) # 18(UTF-8 编码下每个中文字符占 3 字节)
print s[0] # '\xe4',一个字节,不是字符!
str 的 len() 返回字节数,unicode 的 len() 返回字符数。这是两者最本质的区别。
创建 unicode 对象
u1 = u"hello" # 前缀 u 或 U
u2 = u'中文'
u3 = u"""多行
Unicode"""
# 用 unicode() 构造函数
u4 = unicode("hello")
# 从码点创建
u5 = unichr(20320) # u'你',unichr 返回对应码点的 unicode 字符
print ord(u'你') # 20320,返回码点值
unichr() 是 chr() 的 Unicode 版本。chr(65) 返回 'A'(str),unichr(65) 返回 u'A'(unicode)。
编码与解码
unicode 和 str 之间的转换必须显式进行,使用 encode(编码)和 decode(解码):
u = u"你好"
# unicode → str:编码(encode)
s = u.encode('utf-8')
print type(s) # <type 'str'>
print repr(s) # '\xe4\xbd\xa0\xe5\xa5\xbd'
# str → unicode:解码(decode)
u2 = s.decode('utf-8')
print type(u2) # <type 'unicode'>
print u2 # 你好
编码是把字符序列变成字节序列,解码是把字节序列还原成字符序列。这个过程必须指定编码方式(如 UTF-8、GBK、ISO-8859-1),因为同样的字节序列在不同编码下代表完全不同的字符。
常见编码
| 编码 | 特点 | 适用场景 |
|---|---|---|
| UTF-8 | 变长编码,ASCII 字符 1 字节,中文 3 字节 | 互联网标准,强烈推荐 |
| UTF-16 | 固定 2 或 4 字节 | Windows 内部使用 |
| GBK | 中文 2 字节,兼容 ASCII | 中国大陆旧系统 |
| ASCII | 仅 128 个字符 | 纯英文文本 |
| ISO-8859-1 | 西欧语言,1 字节 | 西欧旧系统 |
隐式转换的陷阱
Python 2 允许 str 和 unicode 在某些情况下自动转换,但这往往是 bug 的根源:
s = "hello" # str
u = u"world" # unicode
print s + u # u"helloworld" —— str 被自动解码为 unicode
自动转换时,Python 会使用 sys.getdefaultencoding() 返回的编码(通常是 'ascii')来解码 str。如果 str 包含非 ASCII 字节,就会抛出 UnicodeDecodeError:
s = "你好" # str,UTF-8 编码的字节
u = u"世界" # unicode
print s + u # UnicodeDecodeError: ascii codec can't decode...
错误信息中的 ascii codec 就是默认编码。解决方法是显式指定编码:
s = "你好"
u = u"世界"
print s.decode('utf-8') + u # u"你好世界"
文件头编码声明
为了让 Python 2 正确解析源文件中的非 ASCII 字符,必须在文件第一行或第二行声明编码:
# -*- coding: utf-8 -*-
# 或者
# coding=utf-8
没有这行声明,Python 2 默认使用 ASCII 编码,遇到中文字符会抛出 SyntaxError: Non-ASCII character。
文件读写中的 Unicode
用 open() 读取文件时,返回的是 str(字节)。如果需要 unicode,必须手动解码:
# 读取文件
with open('data.txt', 'r') as f:
s = f.read() # str(字节)
u = s.decode('utf-8') # unicode
# 写入文件
u = u"你好"
with open('output.txt', 'w') as f:
f.write(u.encode('utf-8')) # 必须编码为字节才能写入
Python 2 的 codecs 模块提供了更便捷的 Unicode 文件操作:
import codecs
# 自动解码为 unicode
with codecs.open('data.txt', 'r', 'utf-8') as f:
u = f.read() # 直接返回 unicode
# 自动编码为字节
with codecs.open('output.txt', 'w', 'utf-8') as f:
f.write(u"你好") # 自动编码后写入
最佳实践
- 内部全部使用 unicode:程序内部处理文本时,始终使用
unicode类型。 - 边界处编码/解码:在输入边界(文件、网络、数据库)将字节解码为 unicode;在输出边界将 unicode 编码为字节。
- 统一使用 UTF-8:UTF-8 是互联网的事实标准,能表示所有 Unicode 字符,且与 ASCII 兼容。
- 永远不要混用 str 和 unicode:相加、比较、格式化时,确保两者类型一致。
Python 3 彻底解决了这些问题:str 就是 Unicode,bytes 是独立的字节类型,两者不能隐式混用。理解 Python 2 的 unicode 机制,是理解 Python 3 字符串设计的最佳途径。