struct模块使用

pyton struct模块

struct结构体在c语言中的作用,它定义了一种结构,里面包含不同类型的数据(int,char,bool等等),方便对某一结构对象进行处理。

在网络通信当中,大多传递的数据是以二进制流(binarydata)存在的。

当传递字节串时,不必担心太多的问题; 传递字符串之前也要使用string.encode(‘utf8’)转为字节串
而当传递诸如int、char之类的基本数据的时候,就需要有一种机制将某些特定的结构体类型打包成二进制流的字节串然后再网络传输,而接收端也应该可以通过某种机制进行解包还原出原始的结构体数据。

python中的struct模块就提供了这样的机制,该模块的主要作用就是对python基本类型值与用python字节串格式表示的C struct类型间的转化(This module performs conversions between Python values and C structs represented as Python bytes objects.)

1. 基本的pack和unpack

原型:struct.pack(format, v1, v2, …)
struct.unpack(format, buffer)

使用pack和unpack分别对数据进行打包和解包:

import struct

p_data = struct.pack('i', 25) 
print(p_data)
print(struct.unpack('i', p_data))

p1_data = struct.pack('B', 2)
print(p1_data)
print(struct.unpack('B', p1_data))
print(struct.calcsize('B'))  # 长度为一个字节

# output
'''
b'\x19\x00\x00\x00'
(25,)
b'\x02'
(2,)
1
'''

struct.pack.format为"i"时,只能打包长度为10的数字,超过10位会抛出异常:

p_data = struct.pack('i', 1234567890) 
print(p_data)
print(struct.unpack('i', p_data))
struct.calcsize('i') # 一个'i'转换后的长度为4,2个'i'则为8

# output
'''
b'\xd2\x02\x96I'
(1234567890,)
'''

转换多个数据:

data = struct.pack('hhl', 1, 2, 3)
print(data) # 'hhl'分别是1,2,3的转换格式
print(struct.unpack('hhl', data))
struct.calcsize('hhl') # 以'hhl'格式转换得到的字节串长度

# output
'''
b'\x01\x00\x02\x00\x03\x00\x00\x00'
(1, 2, 3)
'''

2. format和字节顺序参考表

struct模块使用struct模块使用
  • format格式字符前面可以有整数num, 代表有num个需要转换的值,但’s’是例外
print(struct.pack('3c', b'a', b'b', b'c'))  # '3c'代表有3个要转换的字符
print(struct.pack('3s', b'abc')) # 对于's'格式,之前的数字被解释为bytes的长度

字节顺序的大端模式和小端模式

print(struct.pack('>i', 0x12345678)) # 大端模式, 地址从低到高,数据位从高到低
print(struct.pack('<i', 0x12345678)) # 小端模式

3. 使用struct的原因

将int转为bytes方法的速度比较:

import timeit
print(timeit.timeit('bytes([255])', number=1000000))
print(timeit.timeit('struct.pack("B", 255)', setup='import struct', number=1000000))
print(timeit.timeit('(255).to_bytes(1, byteorder="little")', number=1000000))

output

0.1463956000006874
0.08819799999764655
0.1185951000006753

结论: struct.pack() 函数执行整型到字节的转换可获得最佳执行性能

4. 利用buffer,使用pack_into和unpack_from方法

使用二进制打包数据的场景大部分都是对性能要求比较高的使用环境。
而在上面提到的pack方法都是对输入数据进行操作后重新创建了一个内存空间用于返回,也就是说我们每次pack都会在内存中分配出相应的内存资源,这有时是一种很大的性能浪费。

struct模块还提供了pack_into() 和 unpack_from()的方法用来解决这样的问题,也就是对一个已经提前分配好的buffer进行字节的填充,而不会每次都产生一个新对象对字节进行存储。

data = (b'ccc', 25, 38)
buf = bytearray(struct.calcsize('3s2i'))  # 预先创建一个缓冲区buf
struct.pack_into('3s2i', buf, 0, *data)  # 将打包的字节填充到缓冲区,从下标为0的位置开始
struct.unpack_from('3s2i', buf, 0)  # 从缓冲区buf下标为0的位置读取数据,并按格式'3s2i'解包

相比使用pack方法打包,pack_into 方法一直是在对buffer对象进行操作,而不会像pack那样要每次创建内存用于缓存字节串。

使用pack_into的offset参数,可以将多个python对象pack到一个缓冲区对象中,并可利用offset进行unpack:

data = (b'ccc', 25, 38)
data_format = '3s2i'

data2 = (b'cze', 10)
data2_format = '3si'
buf = bytearray(struct.calcsize(data_format) + struct.calcsize(data2_format))
struct.pack_into(data_format, buf, 0, *data)
struct.pack_into(data2_format, buf, struct.calcsize(data_format), *data2)

print(struct.unpack_from(data_format, buf, 0))
print(struct.unpack_from(data2_format, buf, struct.calcsize(data_format)))

# output
'''
(b'ccc', 25, 38)
(b'cze', 10)
'''

5. 使用struct.Struct类简化操作

上面使用原始的函数将多个python对象pack到一个缓冲区队中的代码有些啰嗦,使用struct.Struct类创建一个Struct对象并调用其方法比使用相同格式的struct函数更有效,因为格式字符串只需要编译一次

import struct

data = (b'ccc', 25, 38)
data2 = (b'cze', 10)

s1_obj = struct.Struct('3s2i')
s2_obj = struct.Struct('3si')

buf = bytearray(s1_obj.size + s2_obj.size)

s1_obj.pack_into(buf, 0, *data)
s2_obj.pack_into(buf, s1_obj.size, *data2)

print(s1_obj.unpack_from(buf, 0))
print(s2_obj.unpack_from(buf, s1_obj.size))

# output
'''
(b'ccc', 25, 38)
(b'cze', 10)
'''

参考

浅析Python中的struct模块

上一篇:密码学课程设计-混合加密


下一篇:unity 使用lua处理byte数组