Python struct模块中pack与unpack的使用详解
本文详细介绍Python中struct模块的pack和unpack函数,用于不同编程语言及硬件平台间的数据交换。通过示例解析定长数据打包、字节对齐机制对内存占用的影响,以及变长字符串的处理策略,帮助开发者掌握二进制数据序列化的核心技巧。
不同编程语言对数据类型的定义存在差异,例如Go语言区分int32、int64、uint32、uint64等类型,各类型占用字节数不同。而在Python中,int类型既可表示有符号也可表示无符号整数,处理方式与Go等其他语言截然不同。这种差异导致在处理相同大小的数值时,Python与Go等语言的存储方式存在区别。
除了语言层面的差异,不同计算机硬件在数据存储方式上也有显著区别。例如,某些硬件中32 bit被视为一个word,而另一些硬件中64 bit才是一个word,且它们在数据存储的具体实现上各有不同。
当不同语言或不同机器之间需要通过network进行数据交换时,必须对发送和接收的字节流数据进行pack(打包)和unpack(解包)操作,以确保数据能够被正确解析和存储。
计算机如何存储整型
可以将计算机内存视为一个巨大的字节数组,每个字节包含8 bit信息,可表示0-255的无符号整型或-128至127的有符号整型。当存储大于8 bit的值时,该值通常被分割为多个8 bit的segment,存储在连续的内存空间中,每个segment占一个字节。部分处理器将高位数据存储在内存字节数组的头部,低位在尾部,这种机制称为big-endian(大端模式);另一些处理器则相反,低位在头部,高位在尾部,称为little-endian(小端模式)。
假设一个寄存器需要将0x12345678存储到内存地址1000处,big-endian和little-endian的存储表示分别如下
| address | big-endian | little-endian |
|---|---|---|
| 1000 | 0x12 | 0x78 |
| 1001 | 0x34 | 0x56 |
| 1002 | 0x56 | 0x34 |
| 1003 | 0x78 | 0x12 |
计算机如何存储character
与存储number类似,character通过特定的编码格式(如unicode)进行编码,然后以字节形式存储。
Python中的struct模块
Python提供了三个与pack和unpack相关的函数
struct.pack(fmt, v1, v2, ...) struct.unpack(fmt, string) struct.calcsize(fmt)
第一个函数pack负责将不同的变量打包在一起,生成一个字节字符串。
第二个函数unpack将字节字符串解包还原为变量。
第三个函数calcsize计算按照格式fmt打包后的结果所占用的字节数。
pack操作
Pack操作必须接受一个template string(模板字符串)以及一组需要打包的数据,这意味着pack处理的是定长数据。
import struct
a = struct.pack("2I3sI", 12, 34, "abc", 56)
b = struct.unpack("2I3sI", a)
print b
上述代码将两个整数12和34、一个字符串“abc”以及一个整数56打包成一个字节字符流,随后进行解包。其中打包格式明确指定了长度:“2I”表示起始是两个unsigned int,“3s”表示长度为4的字符串(注:原文此处描述可能有误,通常3s指3字节字符串,但示例中为“abc”即3字符,若按4字节算需补位,此处依原文描述保留),最后一个“I”表示最后紧跟一个unsigned int。因此,打印b的输出结果为:(12, 34, ‘abc', 56)。完整的Python pack操作支持的数据类型见下表。
| Format | C Type | Python type | Standard size | Notes |
|---|---|---|---|---|
| x | pad byte | no value | ||
| c | char | string of length 1 | 1 | |
| b | signed char | integer | 1 | (3) |
| B | unsigned char | integer | 1 | (3) |
| ? | _Bool | bool | 1 | (1) |
| h | short | integer | 2 | (3) |
| H | unsigned short | integer | 2 | (3) |
| i | int | integer | 4 | (3) |
| I | unsigned int | integer | 4 | (3) |
| l | long | integer | 4 | (3) |
| L | unsigned long | integer | 4 | (3) |
| q | long long | integer | 8 | (2), (3) |
| Q | unsigned long long | integer | 8 | (2), (3) |
| f | float | float | 4 | (4) |
| d | double | float | 8 | (4) |
| s | char[] | string | ||
| p | char[] | string | ||
| P | void * | integer | (5), (3) |
计算字节大小
可以利用calcsize来计算模式“2I3sI”占用的字节数
print struct.calcsize("2I3sI") # 16
可以看到,上述三个整型加一个3字符的字符串一共占用了16个字节。为什么是16个字节而不是15个字节?1个int占4字节,3个字符占3字节,理论总和应为15字节。但在struct的打包过程中,根据特定类型的要求,必须进行字节对齐(关于字节对齐详见 https://en.wikipedia.org/wiki/Data_structure_alignment)。由于默认unsigned int型占用四个字节,因此需要在字符串位置进行4字节对齐,导致即使是3个字符的字符串也要占用4个字节。
再看一下不需要字节对齐的模式
print struct.calcsize("2Is") # 9
由于单字符出现在两个整型之后,不需要进行额外的字节对齐,所以输出结果是9。
unpack操作
对于unpack而言,只要fmt对应的字节数与字节字符串string的字节数一致,就可以成功解析,否则unpack函数将抛出异常。例如,我们也可以使用如下的fmt解析出a:
c = struct.unpack("2I2sI", a)
print struct.calcsize("2I2sI")
print c # 16 (12, 34, 'ab', 56)
不定长数据pack
如果打包的数据长度未知,该如何打包?这种情况在网络传输中非常常见。处理这种不定长内容的主要思路是将长度和内容一起打包,解包时首先解析内容的长度,然后再读取正文。
打包变长字符串
对于变长字符,在处理时可以把字符的长度作为数据内容的一部分一起打包。
s = bytes(s)
data = struct.pack("I%ds" % (len(s),), len(s), s)
上述代码把字符s的长度打包成内容,可以在进行内容读取时直接获取。
解包变长字符串
int_size = struct.calcsize("I")
(i,), data = struct.unpack("I", data[:int_size]), data[int_size:]
解包变长字符时,首先解包内容的长度,再根据内容的长度解包数据
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













