struct — 将字节解释为打包的二进制数据

源代码: Lib/struct.py


此模块在 Python 值和表示为 Python bytes 对象的 C 结构之间进行转换。紧凑的格式字符串描述了到/从 Python 值的预期转换。该模块的函数和对象可用于两个截然不同的应用程序:与外部源(文件或网络连接)进行数据交换,或者在 Python 应用程序和 C 层之间进行数据传输。

注意

当未给出前缀字符时,默认使用本机模式。它根据构建 Python 解释器的平台和编译器打包或解包数据。打包给定 C 结构的结果包括用于维护所涉及 C 类型正确对齐的填充字节;类似地,解包时会考虑对齐。相反,当在外部源之间传输数据时,程序员负责定义字节顺序和元素之间的填充。有关详细信息,请参阅字节顺序、大小和对齐

多个 struct 函数(和 Struct 的方法)采用 buffer 参数。这指的是实现缓冲区协议并提供可读或可读写缓冲区的对象。用于此目的的最常见类型是 bytesbytearray,但许多其他可以被视为字节数组的类型都实现了缓冲区协议,因此可以读取/填充它们,而无需从 bytes 对象进行额外复制。

函数和异常

该模块定义了以下异常和函数

exception struct.error

在各种情况下引发的异常;参数是一个描述错误的字符串。

struct.pack(format, v1, v2, ...)

返回一个 bytes 对象,其中包含根据格式字符串 format 打包的值 v1v2、… 。参数必须与格式要求的值完全匹配。

struct.pack_into(format, buffer, offset, v1, v2, ...)

根据格式字符串 format 打包值 v1v2、…,并将打包的字节写入可写缓冲区 buffer,从位置 offset 开始。请注意,offset 是一个必需参数。

struct.unpack(format, buffer)

根据格式字符串 format 从缓冲区 buffer(大概由 pack(format, ...) 打包)解包。结果是一个元组,即使它只包含一个项。缓冲区的字节大小必须与格式要求的大小相匹配,如 calcsize() 反映的那样。

struct.unpack_from(format, /, buffer, offset=0)

根据格式字符串 formatbuffer 中从位置 offset 开始解包。结果是一个元组,即使它只包含一个项。从位置 offset 开始,缓冲区的字节大小必须至少为格式要求的大小,如 calcsize() 反映的那样。

struct.iter_unpack(format, buffer)

根据格式字符串 format 从缓冲区 buffer 迭代解包。此函数返回一个迭代器,它将从缓冲区读取大小相等的块,直到所有内容都被使用。缓冲区的字节大小必须是格式要求大小的倍数,如 calcsize() 反映的那样。

每次迭代都会生成一个由格式字符串指定的元组。

在 3.4 版本中添加。

struct.calcsize(format)

返回与格式字符串 format 对应的结构的大小(以及由此产生的 pack(format, ...) 生成的字节对象的大小)。

格式字符串

格式字符串描述打包和解包数据时的数据布局。它们由格式字符组成,这些字符指定正在打包/解包的数据类型。此外,特殊字符控制字节顺序、大小和对齐。每个格式字符串都由一个可选的前缀字符组成,该字符描述数据的整体属性以及一个或多个格式字符,这些字符描述实际的数据值和填充。

字节顺序、大小和对齐

默认情况下,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 的定义,它始终是大端字节序。

没有办法指示非本地字节顺序(强制字节交换);请使用适当的 '<''>'

备注

  1. 仅在连续的结构成员之间自动添加填充。在编码结构的开头或结尾处不会添加填充。

  2. 当使用非本地大小和对齐方式时,例如使用 ' < '、' > '、' = ' 和 ' ! ' 时,不会添加填充。

  3. 要将结构的末尾与特定类型的对齐要求对齐,请以该类型的代码结束格式,并使用零的重复计数。请参阅示例

格式字符

格式字符具有以下含义;鉴于它们的类型,C 和 Python 值之间的转换应该是显而易见的。“标准大小”列是指使用标准大小时打包值的字节大小;也就是说,当格式字符串以 '<''>''!''=' 之一开头时。当使用本地大小时,打包值的大小取决于平台。

格式

C 类型

Python 类型

标准大小

备注

x

填充字节

无值

(7)

c

char

长度为 1 的字节

1

b

signed char

整数

1

(1), (2)

B

unsigned char

整数

1

(2)

?

_Bool

布尔值

1

(1)

h

short

整数

2

(2)

H

unsigned short

整数

2

(2)

i

int

整数

4

(2)

I

unsigned int

整数

4

(2)

l

long

整数

4

(2)

L

unsigned long

整数

4

(2)

q

long long

整数

8

(2)

Q

unsigned long long

整数

8

(2)

n

ssize_t

整数

(3)

N

size_t

整数

(3)

e

(6)

float

2

(4)

f

float

float

4

(4)

d

double

float

8

(4)

s

char[]

字节

(9)

p

char[]

字节

(8)

P

void*

整数

(5)

在 3.3 版本中更改: 添加了对 'n''N' 格式的支持。

在 3.6 版本中更改: 添加了对 'e' 格式的支持。

备注

  1. '?' 转换代码对应于 C99 标准以来定义的 C 标准 _Bool 类型。在标准模式下,它由一个字节表示。

  2. 当尝试使用任何整数转换代码打包非整数时,如果该非整数具有 __index__() 方法,则会调用该方法将参数转换为整数,然后再打包。

    在 3.2 版本中更改: 为非整数添加了对 __index__() 方法的使用。

  3. 'n''N' 转换代码仅适用于本地大小(选择为默认值或使用 '@' 字节顺序字符)。对于标准大小,您可以使用适合您应用程序的任何其他整数格式。

  4. 对于 'f''d''e' 转换代码,打包的表示形式使用 IEEE 754 binary32、binary64 或 binary16 格式(分别用于 'f''d''e'),而不管平台使用的浮点格式如何。

  5. 'P' 格式字符仅适用于本地字节顺序(选择为默认值或使用 '@' 字节顺序字符)。字节顺序字符 '=' 选择根据主机系统使用小端或大端排序。struct 模块不将其解释为本地排序,因此 'P' 格式不可用。

  6. IEEE 754 binary16“半精度”类型是在 IEEE 754 标准的 2008 年修订版中引入的。它有一个符号位、一个 5 位指数和 11 位精度(显式存储 10 位),并且可以表示大约 6.1e-056.5e+04 之间的全精度数字。C 编译器没有广泛支持此类型:在典型的机器上,无符号短整数可用于存储,但不能用于数学运算。有关更多信息,请参阅 Wikipedia 上关于半精度浮点格式的页面。

  7. 打包时,'x' 插入一个 NUL 字节。

  8. 'p' 格式字符编码一个“Pascal 字符串”,这意味着一个存储在固定数量字节中的短的可变长度字符串,由计数给出。存储的第一个字节是字符串的长度,或 255(以较小者为准)。字符串的字节紧随其后。如果传递给 pack() 的字符串太长(长于计数减 1),则仅存储字符串的前导 count-1 个字节。如果字符串短于 count-1,则用空字节填充,以便总共使用正好计数个字节。请注意,对于 unpack()'p' 格式字符消耗 count 个字节,但返回的字符串永远不能包含超过 255 个字节。

  9. 对于 's' 格式字符,计数被解释为字节的长度,而不是像其他格式字符一样的重复计数;例如,'10s' 表示一个 10 字节的字符串映射到或来自一个 Python 字节字符串,而 '10c' 表示 10 个单独的单字节字符元素(例如,cccccccccc)映射到或来自十个不同的 Python 字节对象。(有关差异的具体演示,请参见示例。)如果未给出计数,则默认为 1。对于打包,该字符串将被截断或用空字节填充以使其适合。对于解包,生成的字节对象始终具有确切的指定字节数。作为一种特殊情况,'0s' 表示一个空的字符串(而 '0c' 表示 0 个字符)。

格式字符前面可以有一个整数重复计数。例如,格式字符串 '4h''hhhh' 的含义完全相同。

格式之间的空格字符将被忽略;但是,计数及其格式不得包含空格。

当使用整数格式('b''B''h''H''i''I''l''L''q''Q')之一打包值 x 时,如果 x 超出了该格式的有效范围,则会引发 struct.error

在 3.1 版本中更改: 以前,某些整数格式包装了超出范围的值并引发了 DeprecationWarning 而不是 struct.error

对于 '?' 格式字符,返回值是 TrueFalse。打包时,使用参数对象的真值。将打包本地或标准布尔表示形式中的 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

假设平台的 long 类型按 4 字节边界对齐,则以下格式 'llh0l' 会在末尾添加两个填充字节。

>>> pack('@llh0l', 1, 2, 3)
b'\x00\x00\x00\x01\x00\x00\x00\x02\x00\x03\x00\x00'

另请参阅

模块 array

同类数据的紧凑二进制存储。

模块 json

JSON 编码器和解码器。

模块 pickle

Python 对象序列化。

应用

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(v1, v2, ...)

与使用已编译格式的 pack() 函数相同。(len(result) 将等于 size。)

pack_into(buffer, offset, v1, v2, ...)

与使用已编译格式的 pack_into() 函数相同。

unpack(buffer)

与使用已编译格式的 unpack() 函数相同。缓冲区的字节大小必须等于 size

unpack_from(buffer, offset=0)

与使用已编译格式的 unpack_from() 函数相同。从位置 *offset* 开始,缓冲区的字节大小必须至少为 size

iter_unpack(buffer)

与使用已编译格式的 iter_unpack() 函数相同。缓冲区的字节大小必须是 size 的倍数。

在 3.4 版本中添加。

format

用于构造此 Struct 对象的格式字符串。

在 3.7 版本中变更: 格式字符串类型现在是 str 而不是 bytes

size

format 对应的结构的计算大小(因此也是 pack() 方法生成的字节对象的大小)。

在 3.13 版本中变更: struct 的 *repr()* 已更改。它现在是

>>> Struct('i')
Struct('i')