高级 Python 开发课程
第 章
>
第 级
序列化模块
结构体模块
目标
使用 struct 模块设置新农田的最终数据细目分解。
在路的尽头有一个服务站,负责管理已经建成的新农田以及已经种植的作物。在这里,我们将检查并处理已种植作物的数据以及农田的预期产出。和本章中的其他关卡一样,我们将处理数据的序列化和反序列化,同时引入最后一个模块——struct 模块。
struct 模块提供了一系列将数据打包为二进制格式的序列化函数,不像我们之前使用的其他模块,它允许你在数据序列化和后续反序列化时,对数据结构进行更细致的控制,从而比其他模块更具灵活性。使用 import struct 来访问下面这些我们将用来处理数据的函数:
struct.calcsize(): 用于确定给定格式字符串打包后的字节数,它接受一个参数,即你想要验证其字节大小的格式。我们将使用的格式有:- 整数:用
'i'表示,用于表示整数数据。 - 浮点数:用
'f'表示,用于表示小数数据。 - 双精度浮点数:用
'd'表示,用于表示更为复杂的小数数据,当浮点数格式不足以满足需求时使用。
- 整数:用
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 # 是一个整数 data_2 = 2.25 # 是一个浮点数 data_3 = 900.702938103 # 是一个双精度浮点数 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)
我们将使用 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() 函数中,以完成关卡。