新的二进制序列类型在很多方面与 Python 2 的 str 类型不同。首先要知道,Python 内置了两种基本的二进制序列类型:Python 3 引入的不可变 bytes 类型和 Python 2.6 添加的可变 bytearray 类型。(Python 2.6 也引入了 bytes 类型,但那只不过是 str 类型的别名,与 Python 3 的 bytes 类型不同。)
bytes 或 bytearray 对象的各个元素是介于 0~255(含)之间的整数,而不像 Python 2 的 str 对象那样是单个的字符。然而,二进制序列的切片始终是同一类型的二进制序列,包括长度为 1 的切片,如示例 4-2 所示。
示例 4-2 包含 5 个字节的
bytes和bytearray对象
>>> cafe = bytes('café', encoding='utf_8') ➊
>>> cafe
b'caf\xc3\xa9'
>>> cafe[0] ➋
99
>>> cafe[:1] ➌
b'c'
>>> cafe_arr = bytearray(cafe)
>>> cafe_arr ➍
bytearray(b'caf\xc3\xa9')
>>> 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]元素。
虽然二进制序列其实是整数序列,但是它们的字面量表示法表明其中有 ASCII 文本。因此,各个字节的值可能会使用下列三种不同的方式显示。
可打印的 ASCII 范围内的字节(从空格到 ~),使用 ASCII 字符本身。
制表符、换行符、回车符和 \ 对应的字节,使用转义序列 \t、\n、\r 和 \\。
其他字节的值,使用十六进制转义序列(例如,\x00 是空字节)。
因此,在示例 4-2 中,我们看到的是 b'caf\xc3\xa9':前 3 个字节 b'caf' 在可打印的 ASCII 范围内,后两个字节则不然。
除了格式化方法(format 和 format_map)和几个处理 Unicode 数据的方法(包括 casefold、isdecimal、isidentifier、isnumeric、isprintable 和 encode)之外,str 类型的其他方法都支持 bytes 和 bytearray 类型。这意味着,我们可以使用熟悉的字符串方法处理二进制序列,如 endswith、replace、strip、translate、upper 等,只有少数几个其他方法的参数是 bytes 对象,而不是 str 对象。此外,如果正则表达式编译自二进制序列而不是字符串,re 模块中的正则表达式函数也能处理二进制序列。Python 3.0~3.4 不能使用 % 运算符处理二进制序列,但是根据“PEP 461—Adding % formatting to bytes and bytearray”(https://www.python.org/dev/peps/pep-0461/),Python 3.5 应该会支持。
二进制序列有个类方法是 str 没有的,名为 fromhex,它的作用是解析十六进制数字对(数字对之间的空格是可选的),构建二进制序列:
>>> bytes.fromhex('31 4B CE A9')
b'1K\xce\xa9'
构建 bytes 或 bytearray 实例还可以调用各自的构造方法,传入下述参数。
一个 str 对象和一个 encoding 关键字参数。
一个可迭代对象,提供 0~255 之间的数值。
一个整数,使用空字节创建对应长度的二进制序列。[Python 3.5 会把这个构造方法标记为“过时的”,Python 3.6 会将其删除。参见“PEP 467—Minor API improvements for binary sequences”(https://www.python.org/dev/peps/pep-0467/)。]
一个实现了缓冲协议的对象(如 bytes、bytearray、memoryview、array.array);此时,把源对象中的字节序列复制到新建的二进制序列中。
使用缓冲类对象构建二进制序列是一种低层操作,可能涉及类型转换。示例 4-3 做了演示。
示例 4-3 使用数组中的原始数据初始化
bytes对象
>>> import array
>>> numbers = array.array('h', [-2, -1, 0, 1, 2]) ➊
>>> octets = bytes(numbers) ➋
>>> octets
b'\xfe\xff\xff\xff\x00\x00\x01\x00\x02\x00' ➌
➊ 指定类型代码 h,创建一个短整数(16 位)数组。
➋ octets 保存组成 numbers 的字节序列的副本。
➌ 这些是表示那 5 个短整数的 10 个字节。
使用缓冲类对象创建 bytes 或 bytearray 对象时,始终复制源对象中的字节序列。与之相反,memoryview 对象允许在二进制数据结构之间共享内存。如果想从二进制序列中提取结构化信息,struct 模块是重要的工具。下一节会使用这个模块处理 bytes 和 memoryview 对象。
struct 模块提供了一些函数,把打包的字节序列转换成不同类型字段组成的元组,还有一些函数用于执行反向转换,把元组转换成打包的字节序列。struct 模块能处理 bytes、bytearray 和 memoryview 对象。
如 2.9.2 节所述,memoryview 类不是用于创建或存储字节序列的,而是共享内存,让你访问其他二进制序列、打包的数组和缓冲中的数据切片,而无需复制字节序列,例如 Python Imaging Library(PIL)2 就是这样处理图像的。
2Pillow(https://pillow.readthedocs.org/en/latest/)是 PIL 最活跃的派生库。
示例 4-4 展示了如何使用 memoryview 和 struct 提取一个 GIF 图像的宽度和高度。
示例 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+\x02\xe6\x00'
>>> struct.unpack(fmt, header) # ➎
(b'GIF', b'89a', 555, 230)
>>> del header # ➏
>>> del img
❶ 结构体的格式:< 是小字节序,3s3s 是两个 3 字节序列,HH 是两个 16 位二进制整数。
❷ 使用内存中的文件内容创建一个 memoryview 对象……
❸ ……然后使用它的切片再创建一个 memoryview 对象;这里不会复制字节序列。
❹ 转换成字节序列,这只是为了显示;这里复制了 10 字节。
❺ 拆包 memoryview 对象,得到一个元组,包含类型、版本、宽度和高度。
❻ 删除引用,释放 memoryview 实例所占的内存。
注意,memoryview 对象的切片是一个新 memoryview 对象,而且不会复制字节序列。[ 本书的技术审校之一 Leonardo Rochael 指出,如果使用 mmap 模块把图像打开为内存映射文件,那么会复制少量字节。本书不会讨论 mmap,如果你经常读取和修改二进制文件,可以阅读“mmap—Memory-mapped file support”(https://docs.python.org/3/library/mmap.html)来进一步学习。]
本书不会深入介绍 memoryview 和 struct 模块,如果要处理二进制数据,可以阅读它们的文档:“Built-in Types » Memory Views”(https://docs.python.org/3/library/stdtypes.html#memory-views)和“struct—Interpret bytes as packed binary data”(https://docs.python.org/3/library/struct.html)。
简要探讨 Python 的二进制序列类型之后,下面说明如何在它们和字符串之间转换。