1 字符问题
“字符串”是个相当简单的概念:一个字符串是一个字符序列。问题出在“字符”的定义上。
在 2015 年,“字符”的最佳定义是 Unicode 字符。因此,从 Python 3 的str 对象中获取的元素是 Unicode 字符,这相当于从 Python 2 的unicode 对象中获取的元素,而不是从 Python 2 的 str 对象中获取的原始字节序列。
Unicode 标准把字符的标识和具体的字节表述进行了如下的明确区分。
字符的标识,即码位,是 0~1 114 111 的数字(十进制),在Unicode 标准中以 4~6 个十六进制数字表示,而且加前缀“U+”。例如,字母 A 的码位是 U+0041,欧元符号的码位是 U+20AC,高音谱号的码位是 U+1D11E。在 Unicode 6.3 中(这是 Python 3.4 使用的标准),约 10% 的有效码位有对应的字符。
字符的具体表述取决于所用的编码。编码是在码位和字节序列之间转换时使用的算法。在 UTF-8 编码中,A(U+0041)的码位编码成单个字节 x41,而在 UTF-16LE 编码中编码成两个字节x41x00。再举个例子,欧元符号(U+20AC)在 UTF-8 编码中是三个字节——xe2x82xac,而在 UTF-16LE 中编码成两个字节:xacx20。
把码位转换成字节序列的过程是编码;把字节序列转换成码位的过程是解码。示例 4-1 阐释了这一区分。
>>> s = 'café' >>> len(s) # ➊ 4 >>> b = s.encode('utf8') # ➋ >>> b b'cafxc3xa9' # ➌ >>> len(b) # ➍ 5 >>> b.decode('utf8') # ➎ 'café
❶ 'café' 字符串有 4 个 Unicode 字符。
❷ 使用 UTF-8 把 str 对象编码成 bytes 对象。
❸ bytes 字面量以 b 开头。
❹ 字节序列 b 有 5 个字节(在 UTF-8 中,“é”的码位编码成两个字节)。
❺ 使用 UTF-8 把 bytes 对象解码成 str 对象。
如果想帮助自己记住 .decode() 和 .encode() 的区别,可以把字节序列想成晦涩难懂的机器磁芯转储,把 Unicode 字符串想成“人类可读”的文本。那么,把字节序列变成人类可读的文本字符串就是解码,而把字符串变成用于存储或传输的字节序列就是编码。
虽然 Python 3 的 str 类型基本相当于 Python 2 的 unicode 类型,只不过是换了个新名称,但是 Python 3 的 bytes 类型却不是把 str 类型换个名称那么简单,而且还有关系紧密的 bytearray 类型。因此,在讨论编码和解码的问题之前,有必要先来介绍一下二进制序列类型。
2 字节概要
新的二进制序列类型在很多方面与 Python 2 的 str 类型不同。首先要知道,Python 内置了两种基本的二进制序列类型:Python 3 引入的不可变bytes 类型和 Python 2.6 添加的可变 bytearray 类型。(Python 2.6 也引入了 bytes 类型,但那只不过是 str 类型的别名,与 Python 3 的bytes 类型不同。)
>>> cafe = bytes('café', encoding='utf_8') ➊ >>> cafe b'cafxc3xa9' >>> cafe[0] ➋ 99 >>> cafe[:1] ➌ b'c' >>> cafe_arr = bytearray(cafe) >>> cafe_arr ➍ bytearray(b'cafxc3xa9') >>> cafe_arr[-1:] ➎ bytearray(b'xa9')
❶ bytes 对象可以从 str 对象使用给定的编码构建。
❷ 各个元素是 range(256) 内的整数。
❸ bytes 对象的切片还是 bytes 对象,即使是只有一个字节的切片。
❹ bytearray 对象没有字面量句法,而是以 bytearray() 和字节序列字面量参数的形式显示。
❺ bytearray 对象的切片还是 bytearray 对象。
my_bytes[0] 获取的是一个整数,而 my_bytes[:1] 返回的是一个长度为 1 的 bytes 对象——这一点应该不会让人意外。s[0] == s[:1] 只对 str 这个序列类型成立。不过,str 类型的这个行为十分罕见。对其他各个序列类型来说,s[i] 返回一个元素,而 s[i:i+1] 返回一个相同类型的序列,里面是 s[i] 元素。
二进制序列有个类方法是 str 没有的,名为 fromhex,它的作用是解析十六进制数字对(数字对之间的空格是可选的),构建二进制序列:
>>> bytes.fromhex('31 4B CE A9') b'1Kxcexa9'
构建 bytes 或 bytearray 实例还可以调用各自的构造方法,传入下述参数。
一个 str 对象和一个 encoding 关键字参数。
一个可迭代对象,提供 0~255 之间的数值。
一个整数,使用空字节创建对应长度的二进制序列。[Python 3.5 会把这个构造方法标记为“过时的”,Python 3.6 会将其删除。参见“PEP 467—Minor API improvements for binarysequences”(https://www.python.org/dev/peps/pep-0467/)。]
一个实现了缓冲协议的对象(如bytes、bytearray、memoryview、array.array);此时,把源对象中的字节序列复制到新建的二进制序列中。
结构体和内存视图
struct 模块提供了一些函数,把打包的字节序列转换成不同类型字段组成的元组,还有一些函数用于执行反向转换,把元组转换成打包的字节序列。struct 模块能处理 bytes、bytearray 和 memoryview 对象。
memoryview 类不是用于创建或存储字节序列的,而是共享内存,让你访问其他二进制序列、打包的数组和缓冲中的数据切片,而无需复制字节序列,例如 Python Imaging Library(PIL) 就是这样处理图像的。
示例 4-4 展示了如何使用 memoryview 和 struct 提取一个 GIF 图像的宽度和高度。
>>> import struct >>> fmt = '<3s3sHH' # ➊ >>> with open('filter.gif', 'rb') as fp: ... img = memoryview(fp.read()) # ➋ ... >>> header = img[:10] # ➌ >>> bytes(header) # ➍ b'GIF89a+x02xe6x00' >>> struct.unpack(fmt, header) # ➎ (b'GIF', b'89a', 555, 230) >>> del header # ➏ >>> del img
❶ 结构体的格式:< 是小字节序,3s3s 是两个 3 字节序列,HH 是两个16 位二进制整数。
❷ 使用内存中的文件内容创建一个 memoryview 对象……
❸ ……然后使用它的切片再创建一个 memoryview 对象;这里不会复制字节序列。
❹ 转换成字节序列,这只是为了显示;这里复制了 10 字节。
❺ 拆包 memoryview 对象,得到一个元组,包含类型、版本、宽度和高度。
❻ 删除引用,释放 memoryview 实例所占的内存。