Python 有一个内置模块 struct,从名字上看这和 C 的结构体有着千丝万缕的联系,C 的结构体是由多个数据组合而成的一种新的数据类型。
typedef struct {
char *name;
int age;
char * gender;
long salary;
} People;
struct 模块也是负责将多个不同类型的数据组合在一起,因为网络传输的数据都是二进制字节流。而 Python 只有字符串可以直接转成字节流,对于整数、浮点数则无能为力了。所以 Python 提供了 struct 模块来帮我们解决这一点,下面来看看它的用法。
打包和解包
struct 模块内部有两个函数用于打包和解包,分别是 pack 和 unpack。
-
pack:将数据打包成二进制字节流;
-
unpack:对二进制字节流进行解包;
import binascii
import struct
# values 包含一个 12 字节的字节串、一个整数、以及一个浮点数。
values = ("古明地觉".encode("utf-8"), 17, 156.7)
# 第一个参数 "12s i f" 表示格式化字符串(format),里面的符号则代表数据的类型
# 12s:12 个字节的字节串、i: 整数、f: 浮点数
# 因此 12s i f 表示打包的数据有三个,分别是:12 个字节的字节串、一个整数、以及一个浮点数
# 中间使用的空格只是用来对表示类型的符号进行分隔,在编译时会被忽略
packed_data = struct.pack("12s i f", *values) # 这里需要使用 * 将元组打开
# 查看打印的结果
print(packed_data)
"""
b'xe5x8fxa4xe6x98x8exe5x9cxb0xe8xa7x89x11x00x00x003xb3x1cC'
"""
# 还可以将打包后的结果转成十六进制, 这样传输起来更加方便
print(binascii.hexlify(packed_data))
"""
b'e58fa4e6988ee59cb0e8a7891100000033b31c43'
"""
代码中的 packed_data 就是打包之后的结果,而我们又将其转成了 16 进制表示。那么问题来了,既然能打包,那么肯定也能解包。
import struct
import binascii
# 之前对打包之后的数据转成 16 进制所得到的结果
data = b'e58fa4e6988ee59cb0e8a7891100000033b31c43'
# 所以可以使用 binascii.unhexlify 将其转回来,得到 struct 打包之后的数据
packed_data = binascii.unhexlify(data)
# 然后调用 struct.unpack 进行解包,打包用的什么格式,解包也用什么格式
# 会得到一个元组,哪怕解包之后只有一个元素,得到的也是元组
values = struct.unpack("12s i f", packed_data)
print(str(values[0], encoding="utf-8")) # 古明地觉
print(values[1]) # 17
print(values[2]) # 156.6999969482422
发送端将数据按照某种格式转成二进制字节流,接收端在接收到数据之后再按照相同的格式转成相应的数据就行。只不过 Python 中,只有字符串可以直接转换成二进制字节流,整数、浮点数则需要借助于 struct 模块。
但是注意:在使用 struct 打包的时候,不能直接对字符串打包,而是需要先将字符串编码成bytes对象。因为中文字符采用不同的编码所占的字节数不同,所以需要先手动编码成 bytes 对象。
整体还是比较简单的,就是将数据按照指定格式进行打包,然后再按照指定格式进行解包。而像 12s、i、f 这些都属于我们定义的格式中的类型指示符,而除了这些指示符之外,还有哪些类型指示符呢?
然后需要注意,我们在进行打包的时候,类型以及个数一定要匹配。
import struct
try:
struct.pack("iii", 1, 2, 3.14)
except Exception as e:
print(e) # required argument is not an integer
# 告诉我们需要的是整数, 但我们传递了浮点数
try:
# iii 表示接收 3 个整数, 但我们只传递了两个
struct.pack("iii", 1, 2)
except Exception as e:
print(e) # pack expected 3 items for packing (got 2)
try:
# iii 表示接收 3 个整数, 但我们却传递了四个
struct.pack("iii", 1, 2, 3, 4)
except Exception as e:
print(e) # pack expected 3 items for packing (got 4)
此外,我们之前说一个长度为 12 的字节串,可以使用 12s 来表示,那么 3s 就表示长度为 3 的字节串。问题来了,i 表示整数,那么3i 表示什么呢?
import struct
try:
struct.pack("3i", 1, 2)
except Exception as e:
print(e) # pack expected 3 items for packing (got 2)
# 告诉我们接收 3 个值, 但是只传递了两个
packed_data = struct.pack("3i", 1, 2, 3)
print(struct.unpack("3i", packed_data)) # (1, 2, 3)
我们看到 3i 在结果上等同于 iii,但对于 s 而言,3s 可不等同于 sss。3s 仍然表示接收一个元素,只不过这个元素是字节串,并且长度为 3。这些细节要注意。
当然对于字符串而言,即使长度不一样也是无所谓的,我们举个例子。
import struct
# 第一个值是整数, 第二个值是字节串(长度应该为3, 但不是3也可以)
packed_data = struct.pack("i3s", 6, b"abcdefg")
print(packed_data) # b'x06x00x00x00abc'
# 我们看到被截断了, 只剩下了 abc
packed_data = struct.pack("i6s", 6, b"abc")
print(packed_data) # b'x06x00x00x00abcx00x00x00'
# 6s 需要字节长度为 6, 但是我们只有三个, 所以在结尾补上了 3 个