杰瑞科技汇

浅析python中的struct模块

struct 模块是 Python 标准库中一个非常重要但又常常被初学者忽略的工具,它的核心功能是 在 Python 的值(如数字、字符串)和 C 语言中的数据类型(如 int, float, char)之间进行转换

浅析python中的struct模块-图1
(图片来源网络,侵删)

struct 模块就像是 Python 和 C 语言世界之间的“翻译官”或“格式化转换器”,它解决了两个核心问题:

  1. 将 Python 数据打包成二进制格式:当你需要将数据写入文件、通过网络发送,或者存储在内存中时,struct 可以将它们转换成紧凑的二进制字节串。
  2. 从二进制格式中解包出 Python 数据:当你从文件或网络中读取一段二进制数据时,struct 可以根据预定义的格式,将其还原成 Python 的原生数据类型。

为什么需要 struct 模块?

想象一个场景:你需要将一个整数 12345 和一个浮点数 89 发送到另一个程序或另一台计算机。

  • 不使用 struct 的方式:你可能将它们转换成字符串 "12345""67.89",用逗号隔开,如 "12345,67.89",然后发送。

    • 缺点
      • 体积大:字符串占用的空间远大于其二进制表示。
      • 解析复杂:接收方需要知道分隔符是什么,然后进行字符串分割、类型转换,容易出错(如果数字本身也包含逗号)。
      • 不高效:字符串处理比二进制数据处理慢。
  • 使用 struct 的方式:你可以将这两个数按照特定的格式“打包”成一个二进制字节串,接收方只需用同样的格式“解包”即可。

    浅析python中的struct模块-图2
    (图片来源网络,侵删)
    • 优点
      • 紧凑高效:二进制数据非常节省空间。
      • 解析简单:接收方无需关心内容,只需按格式解包即可,准确且快速。
      • 跨语言/平台struct 的格式化字符串与 C 语言兼容,使得 Python 可以方便地与其他语言编写的程序进行数据交互。

核心概念:格式化字符串

struct 模块的所有操作都围绕一个核心概念——格式化字符串,这个字符串定义了你要打包或解包的数据的类型、大小和顺序。

格式化字符串由两部分组成:

  1. 字节序:可选,位于开头,用于指定多字节数据(如 int, float)在内存中的存储顺序。

    • 本机顺序(默认)
    • 标准顺序
    • <: 小端序
    • >: 大端序
    • 网络字节序 (等同于 >)
    • 注意:当所有数据类型的大小都是固定的时候(如 b, B, h, H, i, I),可以省略字节序,但如果包含了大小可变的类型(如 s, p),则必须指定字节序。
  2. 格式字符:用于描述单个数据项的类型和大小。

格式字符 C 语言类型 Python 类型 大小 (字节) 描述
x pad byte No value 1 填充字节
c char bytes of length 1 1 单字节字符
b signed char int 1 有符号字节
B unsigned char int 1 无符号字节
h short int 2 短整型
H unsigned short int 2 无符号短整型
i int int 4 整型
I unsigned int int 4 无符号整型
l long int 4 长整型
L unsigned long int 4 无符号长整型
q long long int 8 长长整型
Q unsigned long long int 8 无符号长长整型
f float float 4 单精度浮点数
d double float 8 双精度浮点数
s char[] bytes 字符串,需要指定长度,如 10s
p char[] bytes 指针,Python 中不常用
P void* int 指针,Python 中不常用

重复计数:你可以在格式字符前添加一个数字来表示重复的次数。

  • 4h 表示 4 个短整型。
  • 10s 表示一个长度为 10 的字节串。
  • 2d 表示 2 个双精度浮点数。

核心函数

struct 模块主要有四个核心函数:

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

    • 功能:将一个或多个 Python 值根据 format 格式化字符串打包成一个字节串。
    • 参数
      • format: 格式化字符串。
      • v1, v2, ...: 需要打包的 Python 值,顺序必须与 format 中的格式字符一一对应。
    • 返回值:一个 bytes 对象。
  2. struct.unpack(format, buffer)

    • 功能:从一个字节串 buffer 中,根据 format 格式化字符串解包出 Python 值。
    • 参数
      • format: 格式化字符串。
      • buffer: 一个 bytes 对象或实现了缓冲区协议的对象。
    • 返回值:一个包含解包后值的元组。
  3. struct.pack_into(format, buffer, offset, v1, v2, ...)

    • 功能:将数据打包并直接写入到一个预分配的 buffer(如 bytearray)的指定偏移量 offset 处,这对于高性能操作(如网络编程)非常有用,因为它避免了创建临时的 bytes 对象。
  4. struct.unpack_from(format, buffer, offset=0)

    • 功能:从一个 buffer 的指定偏移量 offset 处开始解包数据,同样,用于高效地从大型二进制数据中提取信息。

代码示例

示例 1:打包和解包基本数据类型

import struct
# 定义要打包的数据
my_int = 12345
my_float = 67.89
# 1. 打包
# 'if' 表示一个整数 和一个浮点数
# 使用 '<' 指定小端序,确保跨平台一致性
packed_data = struct.pack('<if', my_int, my_float)
print(f"原始数据: int={my_int}, float={my_float}")
print(f"打包后的字节串: {packed_data}")
print(f"字节串长度: {len(packed_data)} 字节") # 4 + 4 = 8
# 2. 解包
# 使用完全相同的格式化字符串进行解包
unpacked_data = struct.unpack('<if', packed_data)
print(f"\n解包后的元组: {unpacked_data}")
print(f"解包出的整数: {unpacked_data[0]}")
print(f"解包出的浮点数: {unpacked_data[1]}")
# 验证数据是否一致
assert my_int == unpacked_data[0]
assert my_float == unpacked_data[1]
print("\n数据验证成功!")

输出:

原始数据: int=12345, float=67.89
打包后的字节串: b'\x39\x30\x00\x00\x9a\x99\x99?'
字节串长度: 8 字节
解包后的元组: (12345, 67.890625)
解包出的整数: 12345
解包出的浮点数: 67.890625
数据验证成功!

注意:由于浮点数的精度问题,解包出的 890625 和原始的 89 有微小的差异,这是浮点数运算的常见现象。

示例 2:打包字符串

import struct
# 定义要打包的字符串
my_string = "hello"
my_int = 100
# '10s' 表示一个长度为10的字节串
# 'i' 表示一个整数
# 注意:打包字符串时,Python 3 会自动将其编码为 bytes
packed_data = struct.pack('<10si', my_string.encode('utf-8'), my_int)
print(f"原始数据: string='{my_string}', int={my_int}")
print(f"打包后的字节串: {packed_data}")
print(f"字节串长度: {len(packed_data)} 字节") # 10 + 4 = 14
# 解包
# 注意:解包出的字符串是 bytes,需要手动解码
unpacked_bytes, unpacked_int = struct.unpack('<10si', packed_data)
print(f"\n解包出的字节串: {unpacked_bytes}")
print(f"解包出的整数: {unpacked_int}")
# 解码字符串
unpacked_string = unpacked_bytes.decode('utf-8').rstrip('\x00') # 去掉填充的空字节
print(f"解码后的字符串: '{unpacked_string}'")
assert my_string == unpacked_string
assert my_int == unpacked_int
print("\n数据验证成功!")

实际应用场景

  1. 文件读写:读写二进制文件,如自定义的文件格式、图片文件、可执行文件等,读取一个文件的头部信息,其中包含魔数、版本号、文件大小等固定格式的数据。

  2. 网络通信:在网络编程中,应用程序之间通常需要交换结构化的数据,使用 struct 可以将数据序列化成二进制流进行传输,接收方再反序列化,这是许多网络协议(如二进制 RPC 协议)的基础。

  3. 与硬件设备交互:通过串口(如 pyserial 库)或 USB 与嵌入式设备、传感器通信,这些设备通常只发送和接收二进制数据包,struct 是解析这些数据包的利器。

  4. 性能优化:当需要处理大量数值数据时,将它们存储在 numpy 数组或 array 模块中,并用 struct 进行 I/O 操作,比使用文本文件要快得多,也更节省空间。


struct 模块是 Python 程序员工具箱中的一个强大工具,尤其适用于需要处理二进制数据的场景。

  • 优点

    • 高效:生成的二进制数据紧凑,读写速度快。
    • 精确:严格定义了数据类型和大小,避免了文本解析的歧义。
    • 跨平台:通过指定字节序,可以确保在不同架构的计算机上数据的一致性。
  • 注意事项

    • 格式必须严格匹配:打包和解包必须使用完全相同的格式化字符串,否则会抛出 struct.error 异常或导致数据错乱。
    • 字节序问题:在不同平台间交换数据时,务必显式指定字节序(如 <>),不要依赖本机顺序 。
    • 字符串编码struct 不直接处理字符串的编码/解码,需要手动调用 .encode().decode() 方法。

当你需要让 Python 与 C 代码、二进制文件、网络协议或底层硬件“对话”时,struct 模块就是最直接、最高效的选择。

分享:
扫描分享到社交APP
上一篇
下一篇