"struct" --- 将字节串解读为打包的二进制数据
*******************************************

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

======================================================================

此模块可在 Python 值和以 Python "bytes" 对象表示的 C 结构体之间进行转
换。通过紧凑 格式字符串 描述预期的 Python 值转换目标/来源。 此模块的函
数和对象可被用于两种相当不同的应用程序，与外部源（文件或网络连接）进行
数据交换，或者在 Python 应用和 C 层级之间进行数据传输。

备注:

  当未给出前缀字符时，将默认为原生模式。它会基于构建 Python 解释器的平
  台和编译器来打包和解包数据。打包一个给定 C 结构体的结果包括为所涉及
  的 C 类型保持正确对齐的填充字节；类似地，当解包时也会将对齐纳入考虑
  。 相反地，当在外部源之间进行数据通信时，将由程序员负责定义字节顺序
  和元素之间的填充。请参阅 字节顺序，大小和对齐方式 了解详情。

某些 "struct" 函数（和 "Struct" 的方法）接受一个 *buffer* 参数。这是指
实现了 缓冲协议 并提供可读或可读写缓冲区的对象。用于此目的的最常见类型
是 "bytes" 和 "bytearray"，但许多其他可被视为字节数组的类型也实现了缓
冲区协议，因此它们可以在无需从 "bytes" 对象进行额外复制的情况下被读取/
填充。


函数和异常
==========

此模块定义了下列异常和函数：

exception struct.error

   会在多种场合下被引发的异常；其参数为一个描述错误信息的字符串。

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

   返回一个 bytes 对象，其中包含根据格式字符串 *format* 打包的值 *v1*,
   *v2*, ... 参数个数必须与格式字符串所要求的值完全匹配。

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

   根据格式字符串 *format* 打包 *v1*, *v2*, ... 等值并将打包的字节串写
   入可写缓冲区 *buffer* 从 *offset* 开始的位置。请注意 *offset* 是必
   需的参数。

struct.unpack(format, buffer)

   根据格式字符串 *format* 从缓冲区 *buffer* 解包（假定是由
   "pack(format, ...)" 打包）。 结果为一个元组，即使其只包含一个条目。
   缓冲区的字节大小必须匹配格式所要求的大小，如 "calcsize()" 所示。

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

   对 *buffer* 从位置 *offset* 开始根据格式字符串 *format* 进行解包。
   结果为一个元组，即使其中只包含一个条目。 缓冲区的字节大小从位置
   *offset* 开始必须至少为 "calcsize()" 显示的格式所要求的大小。

struct.iter_unpack(format, buffer)

   根据格式字符串 *format* 以迭代方式从缓冲区 *buffer* 中解包。 此函数
   返回一个迭代器，它将从缓冲区读取大小相等的块直到其所有内容耗尽为止
   。缓冲区的字节大小必须是格式所要求的大小的整数倍，如 "calcsize()"
   所显示的。

   每次迭代将产生一个如格式字符串所指定的元组。

   Added in version 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. 当使用非本机大小和对齐方式即 '<', '>', '=', and '!' 时不会添加任何
   填充。

3. 要将结构的末尾对齐到符合特定类型的对齐要求，请以该类型代码加重复计
   数的零作为格式结束。参见 例子。


格式字符
--------

格式字符具有以下含义；C 和 Python 值之间的按其指定类型的转换应当是相当
明显的。 ‘标准大小’列是指当使用标准大小时以字节表示的已打包值大小；也
就是当格式字符串以 "'<'", "'>'", "'!'" 或 "'='" 之一开头的情况。当使用
本机大小时，已打包值的大小取决于具体的平台。

+----------+----------------------------+----------------------+------------------+--------------+
| 格式     | C 类型                     | Python 类型          | 标准大小         | 备注         |
|==========|============================|======================|==================|==============|
| "x"      | 填充字节                   | 无                   |                  | (7)          |
+----------+----------------------------+----------------------+------------------+--------------+
| "c"      | char                       | 长度为 1 的字节串    | 1                |              |
+----------+----------------------------+----------------------+------------------+--------------+
| "b"      | signed char                | int                  | 1                | (2)          |
+----------+----------------------------+----------------------+------------------+--------------+
| "B"      | unsigned char              | int                  | 1                | (2)          |
+----------+----------------------------+----------------------+------------------+--------------+
| "?"      | _Bool                      | bool                 | 1                | (1)          |
+----------+----------------------------+----------------------+------------------+--------------+
| "h"      | short                      | int                  | 2                | (2)          |
+----------+----------------------------+----------------------+------------------+--------------+
| "H"      | unsigned short             | int                  | 2                | (2)          |
+----------+----------------------------+----------------------+------------------+--------------+
| "i"      | int                        | int                  | 4                | (2)          |
+----------+----------------------------+----------------------+------------------+--------------+
| "I"      | unsigned int               | int                  | 4                | (2)          |
+----------+----------------------------+----------------------+------------------+--------------+
| "l"      | long                       | int                  | 4                | (2)          |
+----------+----------------------------+----------------------+------------------+--------------+
| "L"      | unsigned long              | int                  | 4                | (2)          |
+----------+----------------------------+----------------------+------------------+--------------+
| "q"      | long long                  | int                  | 8                | (2)          |
+----------+----------------------------+----------------------+------------------+--------------+
| "Q"      | unsigned long long         | int                  | 8                | (2)          |
+----------+----------------------------+----------------------+------------------+--------------+
| "n"      | "ssize_t"                  | int                  |                  | (2), (3)     |
+----------+----------------------------+----------------------+------------------+--------------+
| "N"      | "size_t"                   | int                  |                  | (2), (3)     |
+----------+----------------------------+----------------------+------------------+--------------+
| "e"      | _Float16                   | float                | 2                | (4), (6)     |
+----------+----------------------------+----------------------+------------------+--------------+
| "f"      | float                      | float                | 4                | (4)          |
+----------+----------------------------+----------------------+------------------+--------------+
| "d"      | double                     | float                | 8                | (4)          |
+----------+----------------------------+----------------------+------------------+--------------+
| "F"      | float complex              | 复数                 | 8                | (10)         |
+----------+----------------------------+----------------------+------------------+--------------+
| "D"      | double complex             | 复数                 | 16               | (10)         |
+----------+----------------------------+----------------------+------------------+--------------+
| "s"      | char[]                     | 字节串               |                  | (9)          |
+----------+----------------------------+----------------------+------------------+--------------+
| "p"      | char[]                     | 字节串               |                  | (8)          |
+----------+----------------------------+----------------------+------------------+--------------+
| "P"      | void*                      | int                  |                  | (2), (5)     |
+----------+----------------------------+----------------------+------------------+--------------+

在 3.3 版本发生变更: 增加了对 "'n'" 和 "'N'" 格式的支持

在 3.6 版本发生变更: 添加了对 "'e'" 格式的支持。

在 3.14 版本发生变更: 增加了对 "'F'" 和 "'D'" 格式的支持。

参见: "array" 和 ctypes 模块，以及某些第三方模块如 numpy 均使用类似 -- 但
    略有差异的 -- 类型代码。

注意：

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-05" 和 "6.5e+04" 之间的数字。
   此类型并不被 C 编译器广泛支持：它可以作为 _Float16 类型，如果编译器
   支持 C23 标准附件 H 的话。 在一台典型的机器上，可以使用 unsigned
   short 来存储，但不会被用于数学运算。 请参阅 Wikipedia 页面 half-
   precision floating-point format 了解详情。

7. 在打包时，"'x'" 会插入一个 NUL 字节。

8. The "'p'" format character encodes a "Pascal string", meaning a
   short variable-length string stored in a *fixed number of bytes*,
   given by the count. The first byte stored is the length of the
   string, or 255, whichever is smaller.  The bytes of the string
   follow.  If the byte string passed in to "pack()" is too long
   (longer than the count minus 1), only the leading "count-1" bytes
   of the string are stored.  If the byte string is shorter than
   "count-1", it is padded with null bytes so that exactly count bytes
   in all are used.  Note that for "unpack()", the "'p'" format
   character consumes "count" bytes, but that the "bytes" object
   returned can never contain more than 255 bytes. When packing,
   arguments of types "bytes" and "bytearray" are accepted.

9. For the "'s'" format character, the count is interpreted as the
   length of the byte string, not a repeat count like for the other
   format characters; for example, "'10s'" means a single 10-byte
   string mapping to or from a single Python byte string, while
   "'10c'" means 10 separate one byte character elements (e.g.,
   "cccccccccc") mapping to or from ten different Python byte objects.
   (See 例子 for a concrete demonstration of the difference.) If a
   count is not given, it defaults to 1.  For packing, the byte string
   is truncated or padded with null bytes as appropriate to make it
   fit. For unpacking, the resulting "bytes" object always has exactly
   the specified number of bytes.  As a special case, "'0s'" means a
   single, empty byte string (while "'0c'" means 0 characters). When
   packing, arguments of types "bytes" and "bytearray" are accepted.

10. For the "'F'" and "'D'" format characters, the packed
    representation uses the IEEE 754 binary32 and binary64 format for
    components of the complex number, regardless of the floating-point
    format used by the platform. Note that complex types ("F" and "D")
    are available unconditionally, despite complex types being an
    optional feature in C. As specified in the C11 standard, each
    complex type is represented by a two-element C array containing,
    respectively, the real and imaginary parts.

格式字符之前可以带有整数重复计数。例如，格式字符串 "'4h'" 的含义与
"'hhhh'" 完全相同。

格式之间的空白字符会被忽略；但是计数及其格式字符中不可有空白字符。

当使用某一种整数格式 ("'b'", "'B'", "'h'", "'H'", "'i'", "'I'", "'l'",
"'L'", "'q'", "'Q'") 打包值 "x" 时，如果 "x" 在该格式的有效范围之外则
将引发 "struct.error".

在 3.1 版本发生变更: 在之前版本中，某些整数格式包装了超范围的值并会引
发 "DeprecationWarning" 而不是 "struct.error"。

对于 "'?'" 格式字符，返回值为 "True" 或 "False"。在打包时将会使用参数
对象的逻辑值。 以本机或标准 bool 类型表示的 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

以下格式 "'llh0l'" 将会在末尾添加两个填充字节，假定平台的 long 类型按
4 个字节的边界对齐的话:

   >>> 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" 的整数倍。

      Added in version 3.4.

   format

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

      在 3.7 版本发生变更: 格式字符串类型现在是 "str" 而不再是 "bytes"
      。

   size

      计算出对应于 "format" 的结构大小（亦即 "pack()" 方法所产生的字节
      串对象的大小）。

   在 3.13 版本发生变更: 结构体的 *repr()* 已被修改。现在将为：

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