一、struct简单介绍
注意:本文中出现的四个名词同义:二进制流、二进制数组、字节流、字节数组
1.引入
准确地讲,Python没有专门处理字节的数据类型。但由于b'str'
可以表示字节,所以,字节数组=二进制str。而在C语言中,我们可以很方便地用struct(结构体)、union来处理字节,以及字节和int,float的转换。
在Python中,比方说要把一个32位无符号整数变成字节,也就是4个长度的bytes
,你得配合位运算符这么写:
>>> n = 10240099
>>> b1 = (n & 0xff000000) >> 24
>>> b2 = (n & 0xff0000) >> 16
>>> b3 = (n & 0xff00) >> 8
>>> b4 = n & 0xff
>>> bs = bytes([b1, b2, b3, b4])
>>> bs
b'\x00\x9c@c'
非常麻烦。并且有的时候需要用python处理二进制数据,比如,存取文件,socket操作时.这时候,可以使用python的struct模块来完成.可以用 struct来处理c语言中的结构体.
所以Python提供了一个struct
模块来解决bytes
和其他二进制数据类型的转换。
2.什么是struct模块?
-
struct 是 Python 的内置模块
-
在使用 socket 通信的时候, 大多数据的传输都是以二进制流的形式的存在, 而 struct 模块就提供了一种机制, 该机制可以将某些特定的结构体类型打包成二进制流的字符串然后再网络传输,而接收端也应该可以通过某种机制进行解包还原出原始的结构体数据
二、struct的功能
struct模块的基本功能:
-
按指定格式将python数据转换为字节流数据;
-
将字节流数据转换为指定的python数据类型;
-
处理二进制文件数据;
-
处理C语言中的结构体。
三、struct模块的基本使用
1、struct 的使用
struct 模块可以将任意大小的数字转换成一个固定长度(可选择)的 bytes, 这个原理类似于 hash 算法, 不论内容多大, 最终的 hash 值长度不变, 不同的是 是不可逆的, 而且传入的原材料可以是文本、字符串等许多数据类型, struct 可以反解出原来的数据
ps : struct 模块只能转换数字, 不能转换其他的数据类型
2、模块的常用方法
struct模块中最重要的三个函数是pack()
, unpack()
, calcsize()
函数 | return | explain |
---|---|---|
pack(fmt,v1,v2…) | string | 按照给定的格式(fmt),把数据转换成字符串(字节流),并将该字符串返回. |
pack_into(fmt,buffer,offset,v1,v2…) | None | 按照给定的格式(fmt),将数据转换成字符串(字节流),并将字节流写入以offset开始的buffer中.(buffer为可写的缓冲区,可用array模块) |
unpack(fmt,v1,v2…..) | tuple | 按照给定的格式(fmt)解析字节流,并返回解析结果 |
pack_from(fmt,buffer,offset) | tuple | 按照给定的格式(fmt)解析以offset开始的缓冲区,并返回解析结果 |
calcsize(fmt) | size of fmt | 计算给定的格式(fmt)占用多少字节的内存,注意对齐方式 |
3、属性
format | 格式化字符串。 |
---|---|
size | 结构体的大小。 |
4、字节顺序,大小和对齐(Byte Order, Size, and Alignment)
为了同c中的结构体交换数据,还要考虑有的c或c++编译器使用了字节对齐,通常是以4个字节为单位的32位系统,故而struct
根据本地机器字节顺序转换.可以用格式中的第一个字符来改变对齐方式.定义如下:
Character 字符 | Byte order 字节顺序 | Size大小 | Alignment对齐方式 |
---|---|---|---|
@ | native 按原字节 | native 按原字节 | native 按原字节 |
= | native 按原字节 | standard 标准 | none 无 |
< | little-endian 小端 | standard 标准 | none 无 |
> | big-endian 大端 | standard 标准 | none 无 |
! | network (= big-endian) 网络(=大端) | standard 标准 | none 无 |
使用方法是放在format的第一个位置,就像
@5s6sif
5、打包字节长度对照表
字符(Format) | cType c语言类型 | Python type Python类型 | Standard size 标准大小 |
---|---|---|---|
x | pad byte | no value | |
c | char | string of length 1 | 1 |
b | signed char | integer | 1 |
B | unsigned char | integer | 1 |
? | _Bool | bool | 1 |
h | short | integer | 2 |
H | unsigned short | integer | 2 |
i | int | integer | 4 |
I(大写i) | unsigned int | integer | 4 |
l(小写L) | long | integer | 4 |
L | unsigned long | integer | 4 |
q | long long | integer | 8 |
Q | unsigned long long | integer | 8 |
f | float | float | 4 |
d | double | float | 8 |
s | charl | string | |
p | charl | string | |
P | void* | integer |
注意:
_Bool在C99中定义,如果没有这个类型,则将这个类型视为char,一个字节;
q和Q只适用于64位机器;
每个格式前可以有一个数字,表示这个类型的个数,如s格式表示一定长度的字符串,4s表示长度为4的字符串;4i表示四个int;
P用来转换一个指针,其长度和计算机相关;
f和d的长度和计算机相关;
5、pack 和 unpack基本使用
5.1、pack的基本用法:
>>> import struct
>>> struct.pack('>I', 10240099)
b'\x00\x9c@c'
pack
的第一个参数是处理指令,'>I'
的意思是:
>
表示字节顺序是big-endian,也就是网络序,I
表示4字节无符号整数。
后面的参数个数要和处理指令一致。
-
正确使用示例
import struct
res = struct.pack("i",1234566) # 传入的必须是 int 类型
print(res) # b'\x86\xd6\x12\x00' (查看内容)
print(type(res)) # <class 'bytes'> (查看类型)
res2 = struct.unpack("i",res) # 使用什么 Format 打包就用什么解包
print(res2) # (1234566,) (是个元组)
print(type(res2)) # <class 'tuple'> (查看类型)
print(res2[0]) # 1234566
错误示例
-
传入非int类型引发的错误示例
import struct
res=struct.pack("i","淘小欣")
'''
抛出异常:struct.error: required argument is not an integer(参数必须是整数)
'''
5.2、unpack基本使用
-
unpack
把bytes
变成相应的数据类型:
>>> struct.unpack('>IH', b'\xf0\xf0\xf0\xf0\x80\x80')
(4042322160, 32896)
根据>IH
的说明,后面的bytes
依次变为I
:4字节无符号整数和H
:2字节无符号整数。
所以,尽管Python不适合编写底层操作字节流的代码,但在对性能要求不高的地方,利用struct
就方便多了。
-
传入多个值示例:
res = struct.pack("hiq",12,23,451312) # 传入多个值, 并使用不同的 Fromat print(res) # b'\x0c\x00\x00\x00\x17\x00\x00\x00\xf0\xe2\x06\x00\x00\x00\x00\x00' print(type(res)) # <class 'bytes'> a,b,c = struct.unpack("hiq",res) # 使用解压赋值,有几个值就需要有几个 Fromat print(a,b,c) # 12 23 451312
-
打包一个 json 后的信息长度, 在 socket 中可用于发送报头(报头为固定长度)
import struct import json dic= { "header_name":"tes.txt", "total_size":3, "heash":"淘小欣" } res = json.dumps(dic) # 将报头序列化 lens = struct.pack("i",len(res)) # 将报头的长度传入并打包 lens2 = struct.unpack("i",lens) # 假设通信另一端收到打包的二进制,再进行解包拿到长度 print(lens2) # (74,) print(lens[0]) # 74
错误示例:
-
解包时使用的 Format 不一致错误示例
import struct
res = struct.pack("i",123)
res2 = struct.unpack("q",res)
# struct.error: unpack requires a buffer of 8 bytes
-
Fromat 与值不一致错误示例
with open("aaa.txt","wb")as f: for i in range(5): res = struct.pack("i",i) f.write(res) with open("aaa.txt","rb")as f: res = f.read() print(res) a,b,c,d,e= struct.unpack("i",res) # 打包的时候是 5 个值, 解包的时候也要传 5 个值 print(a,b,c,d,e) # 抛出异常 : struct.error: unpack requires a buffer of 4 bytes
五、参考资料
-
官方文档:https://docs.python.org/3/library/struct.html#format-characters
-
https://www.liaoxuefeng.com/wiki/1016959663602400/1017685387246080