Python struct模块详解:二进制数据打包与解包实战
本文深入解析Python中struct模块的核心用法,详细讲解pack()与unpack()函数如何高效处理二进制数据。通过Socket通信示例演示结构体打包,并重点剖析二进制文件读写时文本模式与二进制模式的差异,特别是换行符转换与EOF处理问题,帮助开发者避免常见的二进制文件读取错误。
Python struct模块概述
在Python开发中,处理二进制数据是常见需求,例如文件存储或Socket网络通信。此时,Python内置的struct模块提供了强大的支持,它能够模拟C语言中的结构体操作,方便地处理二进制流。
struct模块主要包含三个核心函数:pack()用于将数据打包,unpack()用于解包数据,以及calcsize()用于计算格式字符串占用的字节数。
# 按照给定的格式(fmt),把数据封装成字符串(实际上是类似于c结构体的字节流)
pack(fmt, v1, v2, ...)
# 按照给定的格式(fmt)解析字节流string,返回解析出来的tuple
unpack(fmt, string)
# 计算给定的格式(fmt)占用多少字节的内存
calcsize(fmt)
Format格式字符串详解
在format字符串(fmt)中,支持多种数据格式,具体对应关系如下表所示:
| 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) |
使用这些格式时,需将其置于fmt字符串的首位,例如组合格式'@5s6sif'。
Socket通信中的结构体处理
以下是具体的应用示例1:
假设定义如下C语言结构体:
struct Header
{
unsigned short id;
char[4] tag;
unsigned int version;
unsigned int count;
}
当通过socket.recv()接收到上述结构体的二进制数据并存储在字符串s中时,可以使用unpack()函数进行解析:
import struct
id, tag, version, count = struct.unpack("!H4s2I", s)
在格式字符串中,'!'代表使用网络字节序进行解析,这是因为网络传输的数据通常遵循网络字节序。随后的'H'代表一个无符号短整型(unsigned short)的id,'4s'代表长度为4字节的字符串,'2I'则表示两个无符号整型(unsigned int)数据。
通过一次unpack调用,id、tag、version和count变量即可获取到对应的信息值。
同样地,也可以方便地将本地数据重新pack为struct格式:
ss = struct.pack("!H4s2I", id, tag, version, count);
pack函数将id、tag、version、count按照指定格式转换为结构体Header,生成的ss是一个字符串(本质上是类似C结构体的字节流),可以通过socket.send(ss)将其发送出去。
数值类型的打包与解包示例
示例2演示了数值类型的打包与解包:
import struct
a=12.34
# 将a变为二进制
bytes=struct.pack('i',a)
此时,bytes变量是一个字符串对象,其字节内容与变量a的二进制存储内容完全一致。
进行反向操作时,若已有二进制数据bytes(即字符串),可以将其转换回Python数据类型:
# 注意,unpack返回的是tuple !!
a,=struct.unpack('i',bytes)
如果涉及多个数据的组合,处理方式如下:
a='hello'
b='world!'
c=2
d=45.123
bytes=struct.pack('5s6sif',a,b,c,d) 此时的bytes即为二进制形式的数据,可以直接写入文件,例如执行binfile.write(bytes)。
随后,当需要读取数据时,可以执行bytes=binfile.read()。
接着通过struct.unpack()将读取的数据解码为Python变量:
a,b,c,d=struct.unpack('5s6sif',bytes)
'5s6sif'被称为fmt(格式化字符串),由数字和字符组成。其中'5s'表示占用5个字符的字符串,'2i'表示2个整数等。以下是可用的字符及其对应的Python类型(ctype):
二进制文件读写注意事项
在处理二进制文件时,需要注意一些关键问题。
处理二进制文件时,应采用如下方式打开文件:
binfile=open(filepath,'rb') #读二进制文件
binfile=open(filepath,'wb') #写二进制文件
那么,使用'rb'或'wb'与使用'r'或'w'打开文件(binfile=open(filepath,'r'))究竟有何区别?
主要区别体现在两个方面:
第一,使用'r'模式时,如果文件中出现'0x1A'字符,系统会将其视为文件结束符(EOF)。而使用'rb'模式则不存在此问题,即如果用二进制写入再用文本模式读取,且文件中包含'0X1A',则只能读出文件的一部分。使用'rb'模式会一直读取到文件末尾。
第二,对于字符串x='abc\ndef',使用len(x)得到的长度为7,其中\n是换行符,实际值为'0X0A'。当使用'w'(文本模式)写入时,在Windows平台上会自动将'0X0A'转换为两个字符'0X0D'和'0X0A',导致文件长度变为8。当使用'r'(文本模式)读取时,又会将其自动转换回原来的换行符。如果改用'wb'(二进制模式)写入,则保持一个字符不变,读取时也是原样读取。因此,如果用文本模式写入而用二进制模式读取,就需要考虑这多出的一个字节('0X0D',即回车符)。在Linux环境下不会发生这种转换,因为Linux仅使用'0X0A'表示换行。
以上就是Python使用struct处理二进制(pack和unpack用法)的详细内容,更多关于python struct处理二进制的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













