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

**源代码:** Lib/struct.py


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

注意

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

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

函数和异常

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

异常 struct.error

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

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

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

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

根据格式字符串 format 打包值 v1v2……,并将打包的字节写入从位置 offset 开始的可写缓冲区 buffer 中。请注意,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

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 定义的 _Bool 类型。如果此类型不可用,则使用 char 模拟。在标准模式下,它始终由一个字节表示。

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

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

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

  9. 对于 `'s'` 格式字符,计数被解释为字节的长度,而不是像其他格式字符那样的重复计数;例如,`'10s'` 表示映射到单个 Python 字节字符串或从单个 Python 字节字符串映射的单个 10 字节字符串,而 `'10c'` 表示映射到十个不同 Python 字节对象或从十个不同 Python 字节对象映射的 10 个单独的单字节字符元素(例如,cccccccccc)。(有关差异的具体演示,请参阅 示例。)如果未给出计数,则默认为 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

假设平台的长整数在 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

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