struct模块用于将Python中的bytes类型对象和C语言中的struct进行转换,一般用于处理存储在文件或网络中的二进制文件,使用struct模块时需要指定一个格式规范,来对字节大小、顺序、对齐等进行约束。
struct.pack(fmt,v1,v2,…):以一个struct格式化字符串以及一个或多个值为参数,返回一个bytes对象,其中存放的是按照该格式规范表示的所有的这些参数值。
struct.unpack(fmt,buffer):以一个格式规范和一个bytes/bytearray对象为参数(缓冲区),返回一个元组,缓冲区的大小必须和格式规范fmt保持一致。
struct.calcsize(fmt):返回格式规范fmt的大小,和pack()打包后的bytes对象大小一致。
格式字符串是在打包和解包数据时用于指定预期布局的机制,它们由格式字符构成,它指定了打包/解压缩数据的类型。另外,还有用于控制字节顺序,大小和对齐的特殊字符。指定的一些值如下,下表来自于python官方文档,更多细节请参考Python官方文档struct模块
Format | C Type | Python type | Standard size | Notes |
---|---|---|---|---|
x | pad byte | no value | ||
c | char | bytes of length | 1 | 1 |
b | signed char | integer | 1 | (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 |
l | long | integer | 4 | (3) |
L | unsigned long | integer | 4 | (3) |
q | long long | integer | 8 | (2), (3) |
Q | unsigned long | long | 8 | (2), (3) |
n | ssize_t | integer | (4) | |
N | size_t integer | integer | (4) | |
e | (7) | float | 2 | (5) |
f | float | float | 4 | (5) |
d | double | float | 8 | (5) |
s | char[] | bytes | ||
p | char[] | bytes | ||
P | void * | integer | (6) |
如:
# 从上表中看出,h在C中表示short,python中表示integer,占2个字节大小,因此返回的bytes对象中包含2*3个字节
>>> struct.pack("hhh",1,2,3)
b'\x01\x00\x02\x00\x03\x00'
>>>
# l在C中表示long型,在python中表示integer,占4个字节,因此返回的不要bytes对象中1,2占分别4个字节,3占2字节
>>> struct.pack("llh",1,2,3)
b'\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00'
>>>
在上表中,c表示一个byte对象,s通常用于对str进行格式规范,10c表示10个byte对象,10s表示10个byte组成的str,如:
>>> struct.pack("4sl","just".encode("utf-8"),3)
b'just\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00'
>>>
>>> struct.pack("4cl","j".encode("utf8"),"j".encode("utf8"),"j".encode("utf8"),"j".encode("utf8"),3)
b'jjjj\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00'
>>>
默认情况下,C语言中的数据类型使用本地格式和顺序表示,并且在必要时会跳过填充字节保持对齐,因此,在将Python和C之间进行转换时,可以使用下表中的字符作为格式规范可以指示这三个属性:
Character | Byte order | Size | Alignment |
---|---|---|---|
@ | native | native | native |
= | native | standard | none |
< | little-endian | standard | none |
> | big-endian | standard | none |
! | network | (= big-endian) | standard |
little-endian和big-endian称为字节序,其中广泛使用的是little-endian,如果格式规范中没有指定字节序,则使用平台本身字节序。如:
>>> struct.pack(",1,2,3)
b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00'
>>> struct.pack(">llh",1,2,3)
b'\x00\x00\x00\x01\x00\x00\x00\x02\x00\x03'
>>>
# 使用平台本地字节顺序、size大小、对齐方式
>>> struct.pack("@llh",1,2,3)
b'\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00'
>>>
下面例子展示了如何通过struct模块将对象存储到二进制文件中,以及在二进制文件中读取对象:
import struct
import sys
import os
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
@property
def age(self):
return self.__age
@age.setter
def age(self, age):
assert isinstance(age, int), "age must be integer"
self.__age = age
# 将对象写入二进制文件中
def export_binary(self, filename):
# 对于str类型,如果不进行该步骤,则会出现export error:argument for 's' must be a bytes object
def packing_string(name):
return name.encode("utf8")
fh = None
try:
# 指定文件操作为二进制写操作
fh = open(filename, "wb")
# self.name为str类型,使用struct.pack8()时,必须将str类型转换为bytes类型,因此使用一个局部方法进行转换
data = packing_string(self.name)
data_len = len(data)
fh.write(struct.pack("<{0}si".format(data_len), data, self.age))
return True
except Exception as err:
print("{0}:export error:{1}".format(os.path.basename(sys.argv[0]), err))
return False
finally:
if fh is not None:
fh.close()
def import_binary(self, filename):
fh = None
try:
# 二进制文件读操作
fh = open(filename, "rb")
if fh is not None:
s = fh.read()
ss = struct.unpack("<{0}si".format(len(s)-4), s)
print(ss) # (b'zhangsan', 21)
return True
except Exception as err:
print("{0}:export error:{1}".format(os.path.basename(sys.argv[0]), err))
return False
finally:
if fh is not None:
fh.close()
if __name__ == "__main__":
s1 = Student("zhangsan", 21)
s2 = Student("Lisi", 23)
s1.export_binary("s1.bin")
s1.import_binary("s1.bin")
参考文档:https://docs.python.org/3/library/struct.html