高级 Python 开发课程
第 章
>
第 级
序列化模块
结构体模块
目标
使用 struct 模块为新农田设置最终数据细分。
在道路尽头有一个服务站,负责管理已建成的新农田和已种植的作物。在这里,我们将检查并处理已种植作物的数据以及农田的产量预估。与本章的其他关卡一样,我们将进行数据的序列化和反序列化,引入最后一个模块:struct 模块。
struct 模块引入了一系列以二进制格式打包数据的序列化函数,与我们之前使用的其他模块不同,它让我们在序列化和反序列化时对数据结构拥有更大的控制,使其比其他模块更灵活。使用 import struct 来访问以下我们将用于处理数据的函数:
struct.calcsize(): 确定给定格式字符串打包后占用多少字节。它接受一个参数,即要验证字节大小的格式。我们将使用的格式有:- integer: 用
'i'表示,用于表示整数数据 - float: 用
'f'表示,用于表示浮点数数据 - double: 用
'd'表示,用于表示更复杂的小数数据,当 float 格式不够用时使用。
- integer: 用
struct.pack(): 将数据序列化为二进制,并按指定格式打包。它可以接受两个或更多参数,第一个是要使用的格式,其后是要序列化的值。可用的格式如上所述,并且你必须根据要序列化的值的数量相应地添加格式字符。struct.unpack(): 将打包的二进制数据反序列化。它接受两个参数:第一个是与序列化时相同的格式,第二个是要反序列化的数据。struct.iter_unpack(): 将打包的二进制数据反序列化。它与struct.unpack()用法相同,但通过循环逐个块地遍历数据。struct.pack_into():struct.pack()的高级版本。它接受四个参数:要使用的格式、要写入的目标缓冲区、缓冲区中的写入起始位置,以及要打包的数据。struct.unpack_from():struct.unpack()的高级版本。它接受三个参数:要使用的格式、要从中解包的缓冲区,以及在缓冲区中要解包的起始位置。这样可以解包特定部分的数据。
走到服务站的灯光 X 标记处,面向桌子,创建三个变量:integer、float 和 double。我们将使用它们通过 struct.calcsize() 函数来验证每种格式的字节大小。对于 integer 变量,调用函数并传入 'i';对于 float 变量,传入 'f';最后对于 double 变量,传入 'd'。例如:integer = struct.calcsize('i')。将这三个变量添加到预先编写的 write() 函数中。
走到金色 X 标记处,使用 read() 函数收集新农田的数据,记下数据点及其格式,主要包括:Resources、Size 和 Estimate。记下后,走到蓝色地毯上方的灯光 X 标记处,创建一个名为 blue_data 的变量。
使用 blue_data 变量来存储 struct.pack() 函数的返回值,传入你之前记录的格式和数值作为参数。在编写格式时,你需要将格式类型“打包”成一个整体单元。例如,整数格式记作 'i',如果有三个整数数据,则写成 'iii'。同理,如果有一个整数、一个浮点数和一个双精度浮点数,则写成 'ifd'。在传入数据时,将它们依次作为参数。整体示例如下:
data_1 = 8 # is an integer data_2 = 2.25 # is a float data_3 = 900.702938103 # is a double blue_data = struct.pack('ifd', data_1, data_2, data_3)
struct.pack() 函数非常灵活,让你可以自定义数据格式并一次性序列化多个数据点。根据上述示例,将之前读取的数据打包。然后使用 display(blue_data) 来查看打包后的数据。
走到蓝色地毯上方的暗色 X 标记处,面向终端,创建一个名为 blue_unpack 的变量,将 struct.unpack() 的返回值存入该变量,参数为打包时使用的相同格式和 blue_data。格式的写法与 struct.pack() 时相同,这样可以以与序列化时相同的方式来反序列化数据。使用 write(blue_unpack) 来验证之前打包的数据。
在同一位置,我们还将使用 struct.iter_unpack() 函数,它的参数与 struct.unpack() 完全相同,但需用在 for 循环中,这使我们能够逐条迭代数据,而不仅仅是一次性输出所有内容。函数示例如下:
for values in struct.iter_unpack(-insert value-, -insert value-): player.speak(values)
我们将使用 player.speak() 函数来显示值;对于 struct.iter_unpack() 中缺失的参数,填入之前 struct.unpack() 中使用的相同参数,因为它们在用法上是同一函数的变体。
走到红色地毯上方的金色 X 标记处,面向桌子,使用 read() 函数查看作物数量。记下每种作物的数量及其格式。然后走到红色地毯上方的灯光 X 标记处,创建一个名为 buffer 的对象,并赋值为 bytearray(16)。这是一个字节集合,在填充数据之前可以对其进行定位,就像一个手动存储数据的“数据仓库”。参数 16 表示该 buffer 对象可存储的字节长度。
使用 struct.pack_into() 函数向你创建的 buffer 对象填充数据。无需为该函数的返回值创建变量,因为该函数会直接将数据插入到 buffer 中。我们将针对之前记录的每个数值调用该函数,并设置相应的参数。
例如,对于第一个作物数值,你会知道它的格式、字节位置和数量。将格式作为第一个参数,将目标序列化的缓冲区位置(即此处的 buffer)作为第二个参数,第三个参数为你希望在 buffer 中写入的起始位置;由于格式会占用一定字节,请确保写入位置未被占用。最后,将要序列化并打包到 buffer 的数值作为最后一个参数。
struct.pack_into('i', buffer, 0, 82)
如前所述,buffer 对象共有 16 个字节,而上述示例中,整数格式占用 4 个字节并写入位置 0,这意味着写入后 buffer 中剩余 12 个字节未被占用(位置 0–3 已被数值 82 占用)。对所有先前读取并记录的三个作物数据重复此操作。然后使用 display(buffer) 来显示序列化后的数据。
走到红色地毯上方的暗色 X 标记处,面向终端,在这里我们将反序列化数据以验证内容是否正确存储。创建三个变量:lettuce、carrots 和 melons,分别解包每个数据点。对于每个变量,将 struct.unpack_from() 的返回值存入,并使用之前记录的相同参数:第一个参数为格式,第二个为要解包的缓冲区 buffer,第三个为解包的起始位置。例如:
lettuce = struct.unpack_from('i', buffer, 0)
此示例演示了对前面打包示例的解包操作,对另外两个变量执行同样操作,并将 lettuce、carrots 和 melons 传入预先编写的 write() 函数,以完成本关。