struct
— 将字节解释为打包的二进制数据¶
源代码: Lib/struct.py
此模块在 Python 值和表示为 Python bytes
对象的 C 结构之间进行转换。紧凑的 格式字符串描述了与 Python 值之间的预期转换。该模块的函数和对象可用于两种截然不同的应用:与外部源(文件或网络连接)进行数据交换,或者在 Python 应用程序和 C 层之间进行数据传输。
备注
如果未给出前缀字符,则默认使用本机模式。它会根据构建 Python 解释器的平台和编译器来打包或解包数据。打包的 C 结构的结果包括填充字节,这些字节会为涉及的 C 类型保留正确的对齐方式;同样,解包时也会考虑对齐。相反,在与外部源通信数据时,程序员负责定义字节顺序和元素之间的填充。有关详细信息,请参阅 字节顺序、大小和对齐。
几个 struct
函数(以及 Struct
的方法)接受一个 buffer 参数。这指的是实现了 Buffer Protocol 并提供可读或可读写缓冲区的对象。最常用于此目的的类型是 bytes
和 bytearray
,但许多其他可以被视为字节数组的对象也实现了缓冲区协议,以便它们可以被读取/填充,而无需从 bytes
对象中进行额外的复制。
函数和异常¶
该模块定义了以下异常和函数
- exception struct.error¶
在各种情况下引发的异常;参数是描述问题所在的一个字符串。
- struct.pack(format, v1, v2, ...)¶
返回一个包含值 v1, v2, ... 的 bytes 对象,这些值根据格式字符串 format 进行打包。参数必须与格式所要求的数值完全匹配。
- struct.pack_into(format, buffer, offset, v1, v2, ...)¶
根据格式字符串 format 打包值 v1, v2, ...,并将打包的字节写入从位置 offset 开始的可写缓冲区 buffer。请注意,offset 是一个必需参数。
- struct.unpack(format, buffer)¶
从缓冲区 buffer(可能由
pack(format, ...)
打包)中根据格式字符串 format 进行解包。结果是一个元组,即使它只包含一个项目。缓冲区的字节数必须与格式所需的大小匹配,如calcsize()
所反映的。
- struct.unpack_from(format, buffer, offset=0)¶
从缓冲区 buffer 从位置 offset 开始,根据格式字符串 format 进行解包。结果是一个元组,即使它只包含一个项目。缓冲区中的字节数,从位置 offset 开始,必须至少等于格式所需的大小,如
calcsize()
所反映的。
- struct.iter_unpack(format, buffer)¶
从缓冲区 buffer 中根据格式字符串 format 进行迭代解包。此函数返回一个迭代器,该迭代器将从缓冲区中读取等大小的块,直到消耗完其所有内容。缓冲区的字节数必须是格式所需大小的倍数,如
calcsize()
所反映的。每次迭代都根据格式字符串生成一个元组。
在 3.4 版本加入。
- struct.calcsize(format)¶
返回对应于格式字符串 format 的结构(以及因此由
pack(format, ...)
生成的 bytes 对象)的大小。
格式字符串¶
格式字符串描述了打包和解包数据时的数据布局。它们由 格式字符 构成,这些字符指定了正在打包/解包的数据类型。此外,特殊字符控制 字节顺序、大小和对齐。每个格式字符串由一个可选的前缀字符(描述数据的整体属性)和一个或多个格式字符(描述实际数据值和填充)组成。
字节顺序、大小和对齐¶
默认情况下,C 类型以机器的原生格式和字节顺序表示,并通过跳过填充字节(如果需要,根据 C 编译器使用的规则)进行正确对齐。这种行为被选择的目的是使打包的结构字节与相应 C 结构的内存布局完全匹配。是使用原生字节顺序和填充还是标准格式取决于应用程序。
或者,格式字符串的第一个字符可以用于根据下表指示打包数据的字节顺序、大小和对齐方式:
字符 |
字节顺序 |
大小 |
对齐 |
---|---|---|---|
|
原生 |
原生 |
原生 |
|
原生 |
标准 |
无 |
|
小端 |
标准 |
无 |
|
大端 |
标准 |
无 |
|
网络 (= 大端) |
标准 |
无 |
如果第一个字符不是其中之一,则假定为 '@'
。
备注
数字 1023(十六进制为 0x3ff
)具有以下字节表示:
大端(
>
)的03 ff
小端(
<
)的ff 03
Python 示例
>>> import struct
>>> struct.pack('>h', 1023)
b'\x03\xff'
>>> struct.pack('<h', 1023)
b'\xff\x03'
原生字节顺序是大端或小端,具体取决于主机系统。例如,Intel x86、AMD64 (x86-64) 和 Apple M1 是小端;IBM z 和许多旧架构是大端。使用 sys.byteorder
来检查系统的字节序。
原生大小和对齐是使用 C 编译器的 sizeof
表达式确定的。这始终与原生字节顺序结合使用。
标准大小仅取决于格式字符;请参阅 格式字符 部分的表格。
请注意 '@'
和 '='
之间的区别:两者都使用原生字节顺序,但后者的尺寸和对齐是标准化的。
'!'
形式表示网络字节顺序,根据 IETF RFC 1700 定义,网络字节顺序始终是大端。
无法指示非原生字节顺序(强制字节交换);请使用 '<'
或 '>'
的适当选择。
备注
填充仅在连续的结构成员之间自动添加。在编码结构的开头或结尾不添加填充。
使用非原生大小和对齐(例如,使用‘<’、‘>’、‘=’和‘!’)时,不添加填充。
为了将结构末尾对齐到特定类型的对齐要求,请在格式字符串末尾加上该类型的代码,重复次数为零。请参阅 示例。
格式字符¶
格式字符的含义如下;C 和 Python 值之间的转换根据其类型应该是显而易见的。‘标准大小’列是指使用标准大小时打包值的字节大小;也就是说,当格式字符串以 '<'
、'>'
、'!'
或 '='
开头时。使用原生大小时,打包值的尺寸取决于平台。
格式 |
C 类型 |
Python 类型 |
标准大小 |
备注 |
---|---|---|---|---|
|
填充字节 |
无值 |
(7) |
|
|
char |
长度为 1 的字节 |
1 |
|
|
signed char |
整数 |
1 |
(1), (2) |
|
unsigned char |
整数 |
1 |
(2) |
|
_Bool |
bool |
1 |
(1) |
|
short |
整数 |
2 |
(2) |
|
unsigned short |
整数 |
2 |
(2) |
|
int |
整数 |
4 |
(2) |
|
unsigned int |
整数 |
4 |
(2) |
|
long |
整数 |
4 |
(2) |
|
unsigned long |
整数 |
4 |
(2) |
|
long long |
整数 |
8 |
(2) |
|
unsigned long long |
整数 |
8 |
(2) |
|
|
整数 |
(3) |
|
|
|
整数 |
(3) |
|
|
(6) |
浮点数 |
2 |
(4) |
|
浮点数 |
浮点数 |
4 |
(4) |
|
double |
浮点数 |
8 |
(4) |
|
float complex |
complex |
8 |
(10) |
|
double complex |
complex |
16 |
(10) |
|
char[] |
bytes |
(9) |
|
|
char[] |
bytes |
(8) |
|
|
void* |
整数 |
(5) |
已于 3.3 版本更改: 增加了对 'n'
和 'N'
格式的支持。
已于 3.6 版本更改: 增加了对 'e'
格式的支持。
已于 3.14 版本更改: 增加了对 'F'
和 'D'
格式的支持。
备注
'?'
转换码对应于 C 标准自 C99 以来的 _Bool 类型。在标准模式下,它由一个字节表示。当尝试使用任何整数转换码打包非整数时,如果非整数具有
__index__()
方法,则会调用该方法将参数转换为整数后再进行打包。已于 3.2 版本更改: 增加了对非整数使用
__index__()
方法的支持。'n'
和'N'
转换码仅适用于原生大小(通过默认选择或使用'@'
字节顺序字符)。对于标准大小,可以使用适合您应用程序的其他整数格式。对于
'f'
、'd'
和'e'
转换码,打包的表示形式使用 IEEE 754 binary32、binary64 或 binary16 格式(分别对应于'f'
、'd'
或'e'
),而与平台使用的浮点格式无关。'P'
格式字符仅适用于原生字节顺序(通过默认选择或使用'@'
字节顺序字符)。字节顺序字符'='
根据主机系统选择使用小端或大端字节序。struct 模块不会将其解释为原生顺序,因此'P'
格式不可用。IEEE 754 binary16“半精度”类型是在 IEEE 754 标准 2008 年修订版中引入的。它有一个符号位、一个 5 位指数和 11 位精度(显式存储 10 位),并且可以以全精度表示大约
6.1e-05
到6.5e+04
之间的数字。此类型不受 C 编译器广泛支持:在典型机器上,可以使用 unsigned short 进行存储,但不能用于数学运算。有关更多信息,请参阅维基百科上的 半精度浮点格式 页面。打包时,
'x'
插入一个 NUL 字节。'p'
格式字符编码“Pascal 字符串”,表示一个短的变长字符串,存储在固定数量的字节中,该数量由计数指定。存储的第一个字节是字符串的长度,或者 255,取两者中较小者。字符串的字节跟在后面。如果传递给pack()
的字符串太长(长于计数减 1),则只存储字符串的前count-1
个字节。如果字符串比count-1
短,则用空字节填充,以确保总共使用 count 个字节。请注意,对于unpack()
,'p'
格式字符消耗count
个字节,但返回的字符串永远不会包含超过 255 个字节。对于
's'
格式字符,计数被解释为字节的长度,而不是像其他格式字符那样是重复计数;例如,'10s'
表示一个单个的 10 字节字符串,映射到一个 Python 字节字符串,而'10c'
表示 10 个单独的单字节字符元素(例如,cccccccccc
),映射到十个不同的 Python 字节对象。(请参阅 示例 以获得明确的差异演示。)如果未给出计数,则默认为 1。对于打包,字符串将被截断或用空字节填充(根据需要),以使其适合。对于解包,结果的 bytes 对象始终具有指定的字节数。作为特殊情况,'0s'
表示一个单独的空字符串(而'0c'
表示 0 个字符)。对于
'F'
和'D'
格式字符,打包的表示形式使用 IEEE 754 binary32 和 binary64 格式作为复数的组成部分,而与平台使用的浮点格式无关。请注意,复数类型(F
和D
)是无条件可用的,尽管复数类型在 C 中是可选功能。如 C11 标准中所述,每个复数类型由一个两元素的 C 数组表示,分别包含实部和虚部。
格式字符前面可以有一个整数重复计数。例如,格式字符串 '4h'
与 'hhhh'
的含义完全相同。
格式之间的空白字符被忽略;计数及其格式不得包含空格。
当使用整数格式('b'
、'B'
、'h'
、'H'
、'i'
、'I'
、'l'
、'L'
、'q'
、'Q'
)之一打包值 x
时,如果 x
超出了该格式的有效范围,则会引发 struct.error
。
已于 3.1 版本更改: 以前,一些整数格式会包装越界值,并引发 DeprecationWarning
而不是 struct.error
。
对于 '?'
格式字符,返回值是 True
或 False
。打包时,使用参数对象的布尔值。原生或标准布尔表示中的 0 或 1 将被打包,而任何非零值在解包时都将是 True
。
示例¶
备注
原生字节顺序示例(由 '@'
格式前缀或缺少任何前缀字符指定)可能与读者机器的输出不匹配,因为这取决于平台和编译器。
使用大端字节序打包和解包三种不同大小的整数
>>> from struct import *
>>> pack(">bhl", 1, 2, 3)
b'\x01\x00\x02\x00\x00\x00\x03'
>>> unpack('>bhl', b'\x01\x00\x02\x00\x00\x00\x03')
(1, 2, 3)
>>> calcsize('>bhl')
7
尝试打包一个对于定义的字段来说过大的整数
>>> pack(">h", 99999)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
struct.error: 'h' format requires -32768 <= number <= 32767
演示 's'
和 'c'
格式字符之间的区别
>>> pack("@ccc", b'1', b'2', b'3')
b'123'
>>> pack("@3s", b'123')
b'123'
可以通过将解包的字段分配给变量或将结果包装在命名元组中来命名
>>> record = b'raymond \x32\x12\x08\x01\x08'
>>> name, serialnum, school, gradelevel = unpack('<10sHHb', record)
>>> from collections import namedtuple
>>> Student = namedtuple('Student', 'name serialnum school gradelevel')
>>> Student._make(unpack('<10sHHb', record))
Student(name=b'raymond ', serialnum=4658, school=264, gradelevel=8)
在原生模式下,格式字符的顺序可能会影响大小,因为填充是隐式的。在标准模式下,用户负责插入任何所需的填充。请注意,在下面的第一个 pack
调用中,在打包的 '#'
之后添加了三个 NUL 字节,以便将后续的整数对齐到四字节边界。在此示例中,输出是在小端机器上生成的
>>> pack('@ci', b'#', 0x12131415)
b'#\x00\x00\x00\x15\x14\x13\x12'
>>> pack('@ic', 0x12131415, b'#')
b'\x15\x14\x13\x12#'
>>> calcsize('@ci')
8
>>> calcsize('@ic')
5
假定平台的长整型对齐在 4 字节边界上,下面的格式 'llh0l'
在末尾添加了两个填充字节
>>> pack('@llh0l', 1, 2, 3)
b'\x00\x00\x00\x01\x00\x00\x00\x02\x00\x03\x00\x00'
应用¶
struct
模块存在两种主要应用:应用程序内 Python 代码和 C 代码之间的数据交换(原生格式),以及使用约定好的数据布局的应用程序之间的数据交换(标准格式)。总的来说,为这两个领域构建的格式字符串是不同的。
原生格式¶
在构造模仿原生布局的格式字符串时,编译器和机器架构决定了字节顺序和填充。在这种情况下,应使用 @
格式字符来指定原生字节顺序和数据大小。内部填充字节通常会自动插入。可能需要在格式字符串的末尾使用零重复格式代码,以向上舍入到正确的字节边界,从而正确对齐连续的数据块。
考虑以下两个简单的示例(在 64 位小端机器上)
>>> calcsize('@lhl')
24
>>> calcsize('@llh')
18
在没有额外填充的情况下,数据在第二个格式字符串的末尾不会填充到 8 字节边界。零重复格式代码可以解决这个问题
>>> calcsize('@llh0l')
24
'x'
格式代码可用于指定重复,但对于原生格式,最好使用零重复格式,如 '0l'
。
默认情况下,使用原生字节顺序和对齐,但最好明确指定并使用 '@'
前缀字符。
标准格式¶
在交换进程外数据(例如网络或存储)时,请务必精确。指定确切的字节顺序、大小和对齐。不要假设它们与特定机器的原生顺序匹配。例如,网络字节顺序是大端,而许多流行的 CPU 是小端。通过明确定义这一点,用户无需关心其代码运行的平台的具体细节。第一个字符通常应该是 <
或 >
(或 !
)。填充是程序员的责任。零重复格式字符将不起作用。相反,用户必须在需要的地方显式添加 'x'
填充字节。回顾上一节的示例,我们有
>>> calcsize('<qh6xq')
24
>>> pack('<qh6xq', 1, 2, 3) == pack('@lhl', 1, 2, 3)
True
>>> calcsize('@llh')
18
>>> pack('@llh', 1, 2, 3) == pack('<qqh', 1, 2, 3)
True
>>> calcsize('<qqh6x')
24
>>> calcsize('@llh0l')
24
>>> pack('@llh0l', 1, 2, 3) == pack('<qqh6x', 1, 2, 3)
True
以上结果(在 64 位机器上执行)在不同机器上执行时可能不匹配。例如,以下示例在 32 位机器上执行
>>> calcsize('<qqh6x')
24
>>> calcsize('@llh0l')
12
>>> pack('@llh0l', 1, 2, 3) == pack('<qqh6x', 1, 2, 3)
False
类¶
struct
模块还定义了以下类型
- class struct.Struct(format)¶
返回一个新的 Struct 对象,该对象根据格式字符串 format 读写二进制数据。一次创建
Struct
对象并调用其方法比使用相同格式调用模块级函数更有效,因为格式字符串只编译一次。备注
传递给模块级函数的最新格式字符串的已编译版本会缓存起来,因此只使用少量格式字符串的程序无需担心重用单个
Struct
实例。已编译的 Struct 对象支持以下方法和属性
- pack_into(buffer, offset, v1, v2, ...)¶
与
pack_into()
函数相同,使用已编译的格式。
- unpack_from(buffer, offset=0)¶
与
unpack_from()
函数相同,使用已编译的格式。缓冲区中的字节数,从位置 offset 开始,必须至少等于size
。
- iter_unpack(buffer)¶
与
iter_unpack()
函数相同,使用已编译的格式。缓冲区中的字节数必须是size
的倍数。在 3.4 版本加入。
- format¶
用于构造此 Struct 对象的格式字符串。
已于 3.13 版本更改: Struct 的 repr() 已更改。现在是
>>> Struct('i') Struct('i')