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

struct 模块就像是 Python 和 C 语言世界之间的“翻译官”或“格式化转换器”,它解决了两个核心问题:
- 将 Python 数据打包成二进制格式:当你需要将数据写入文件、通过网络发送,或者存储在内存中时,
struct可以将它们转换成紧凑的二进制字节串。 - 从二进制格式中解包出 Python 数据:当你从文件或网络中读取一段二进制数据时,
struct可以根据预定义的格式,将其还原成 Python 的原生数据类型。
为什么需要 struct 模块?
想象一个场景:你需要将一个整数 12345 和一个浮点数 89 发送到另一个程序或另一台计算机。
-
不使用
struct的方式:你可能将它们转换成字符串"12345"和"67.89",用逗号隔开,如"12345,67.89",然后发送。- 缺点:
- 体积大:字符串占用的空间远大于其二进制表示。
- 解析复杂:接收方需要知道分隔符是什么,然后进行字符串分割、类型转换,容易出错(如果数字本身也包含逗号)。
- 不高效:字符串处理比二进制数据处理慢。
- 缺点:
-
使用
struct的方式:你可以将这两个数按照特定的格式“打包”成一个二进制字节串,接收方只需用同样的格式“解包”即可。
(图片来源网络,侵删)- 优点:
- 紧凑高效:二进制数据非常节省空间。
- 解析简单:接收方无需关心内容,只需按格式解包即可,准确且快速。
- 跨语言/平台:
struct的格式化字符串与 C 语言兼容,使得 Python 可以方便地与其他语言编写的程序进行数据交互。
- 优点:
核心概念:格式化字符串
struct 模块的所有操作都围绕一个核心概念——格式化字符串,这个字符串定义了你要打包或解包的数据的类型、大小和顺序。
格式化字符串由两部分组成:
-
字节序:可选,位于开头,用于指定多字节数据(如
int,float)在内存中的存储顺序。- 本机顺序(默认)
- 标准顺序
<: 小端序>: 大端序- 网络字节序 (等同于
>) - 注意:当所有数据类型的大小都是固定的时候(如
b,B,h,H,i,I),可以省略字节序,但如果包含了大小可变的类型(如s,p),则必须指定字节序。
-
格式字符:用于描述单个数据项的类型和大小。
| 格式字符 | 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 模块主要有四个核心函数:
-
struct.pack(format, v1, v2, ...)- 功能:将一个或多个 Python 值根据
format格式化字符串打包成一个字节串。 - 参数:
format: 格式化字符串。v1, v2, ...: 需要打包的 Python 值,顺序必须与format中的格式字符一一对应。
- 返回值:一个
bytes对象。
- 功能:将一个或多个 Python 值根据
-
struct.unpack(format, buffer)- 功能:从一个字节串
buffer中,根据format格式化字符串解包出 Python 值。 - 参数:
format: 格式化字符串。buffer: 一个bytes对象或实现了缓冲区协议的对象。
- 返回值:一个包含解包后值的元组。
- 功能:从一个字节串
-
struct.pack_into(format, buffer, offset, v1, v2, ...)- 功能:将数据打包并直接写入到一个预分配的
buffer(如bytearray)的指定偏移量offset处,这对于高性能操作(如网络编程)非常有用,因为它避免了创建临时的bytes对象。
- 功能:将数据打包并直接写入到一个预分配的
-
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数据验证成功!")
实际应用场景
-
文件读写:读写二进制文件,如自定义的文件格式、图片文件、可执行文件等,读取一个文件的头部信息,其中包含魔数、版本号、文件大小等固定格式的数据。
-
网络通信:在网络编程中,应用程序之间通常需要交换结构化的数据,使用
struct可以将数据序列化成二进制流进行传输,接收方再反序列化,这是许多网络协议(如二进制 RPC 协议)的基础。 -
与硬件设备交互:通过串口(如
pyserial库)或 USB 与嵌入式设备、传感器通信,这些设备通常只发送和接收二进制数据包,struct是解析这些数据包的利器。 -
性能优化:当需要处理大量数值数据时,将它们存储在
numpy数组或array模块中,并用struct进行 I/O 操作,比使用文本文件要快得多,也更节省空间。
struct 模块是 Python 程序员工具箱中的一个强大工具,尤其适用于需要处理二进制数据的场景。
-
优点:
- 高效:生成的二进制数据紧凑,读写速度快。
- 精确:严格定义了数据类型和大小,避免了文本解析的歧义。
- 跨平台:通过指定字节序,可以确保在不同架构的计算机上数据的一致性。
-
注意事项:
- 格式必须严格匹配:打包和解包必须使用完全相同的格式化字符串,否则会抛出
struct.error异常或导致数据错乱。 - 字节序问题:在不同平台间交换数据时,务必显式指定字节序(如
<或>),不要依赖本机顺序 。 - 字符串编码:
struct不直接处理字符串的编码/解码,需要手动调用.encode()和.decode()方法。
- 格式必须严格匹配:打包和解包必须使用完全相同的格式化字符串,否则会抛出
当你需要让 Python 与 C 代码、二进制文件、网络协议或底层硬件“对话”时,struct 模块就是最直接、最高效的选择。
