读写二进制结构的数组
一般情况下来说,我们可以使用struct模块,比如我们可以像这样将一列Python元组写入到一个二进制文件之中,通过struct模块将每个元组编码为一个结构。
from
当然,如果要将这个文件重新读取为一列Python元组的话,有好几种方法来实现。首先,如果我们打算按块以增量的方式读取文件的话,可以这样做
from
如果只是想用一个read()调用将文件全部读取到一个字节中,然后再一块一块的做转换,那么可以这样写:
from
对于那些必须对二进制数据编码和解码的程序,我们都会用到struct模块。要定义一个新的结构,只要简单地创建一个Struct实例就好。
结构总是通过一组结构化的代码来进行定义。这些代码同特定的二进制数据来相互对应,比如32位整数,64位浮点数,32位浮点数等。而第一个字符 < 指定了字节序。在这个例子中表示为“小端序”,将自读修改为 > 就表示为大端序,或者用 !来表示网络字节序
得到的Struct实例有着许多种属性和方法,它们可以用来操纵那种类型的结构。size属性包含了以字节为单位的结构体大小,这对于I/O操作来说是很有用的。pack()和unpack()方法是用来打包和解包数据的:
>>>
创建一个可迭代对象的原因之一就在于这样做允许我们通过一个生成器表达式来创建records记录。在函数unpack_records()中我们采用了另一种方法。这里使用的unpack_from()方法对于从大型的二进制数组中提取出二进制数据是非常有用的,因为它不会创建任何临时对象或者是执行内存拷贝动作,我们只需要提供一个字节串再加上一个字节偏移量,它就能直接从那个位置将字段解包出来.
最后请注意,如果面对的任务是从某种已知的文件结构中读取二进制数据,请先检查是否已经有对应的Python模块可以用,没必要再去重复造轮子了。
读取嵌套型和大小可变的二进制结构
struct模块可以用来编码和解码几乎任何类型的二进制数据结构,这些数据中包含有一系列的嵌套的或者大小可变的记录。假设你用下面的Python数据结构 来表示一个组成一系列多边形的点的集合:
polys
现在假设这个数据被编码到一个以下列头部开始的二进制文件中去了:
紧跟着头部是一系列的多边形记录,编码格式如下:
为了写这样的文件,你可以使用如下的Python代码:
import
将数据读取回来的时候,可以利用函数 struct.unpack() ,代码很相似,基本就是上面写操作的逆序:
def
尽管这个代码可以工作,但是里面混杂了很多读取、解包数据结构和其他细节的代码。如果用这样的代码来处理真实的数据文件, 那未免也太繁杂了点。因此很显然应该有另一种解决方法可以简化这些步骤,让程序员只关注自最重要的事情。
首先,当读取字节数据的时候,通常在文件开始部分会包含文件头和其他的数据结构。 尽管struct模块可以解包这些数据到一个元组中去,另外一种表示这种信息的方式就是使用一个类。 就像下面这样:
import
这里我们使用了一个描述器来表示每个结构字段,每个描述器包含一个结构兼容格式的代码以及一个字节偏移量, 存储在内部的内存缓冲中。在 __get__() 方法中,struct.unpack_from() 函数被用来从缓冲中解包一个值,省去了额外的分片或复制操作步骤。 Structure 类就是一个基础类,接受字节数据并存储在内部的内存缓冲中,并被 StructField 描述器使用。 这里使用了 memoryview() 。
使用这个代码,你现在就能定义一个高层次的结构对象来表示上面表格信息所期望的文件格式。例如:
class
这个很有趣,不过这种方式还是有一些烦人的地方。首先,尽管你获得了一个类接口的便利, 但是这个代码还是有点臃肿,还需要使用者指定很多底层的细节(比如重复使用 StructField ,指定偏移量等)。 另外,返回的结果类同样确实一些便利的方法来计算结构的总数。 任何时候只要你遇到了像这样冗余的类定义,你应该考虑下使用类装饰器或元类。 元类有一个特性就是它能够被用来填充许多低层的实现细节,从而释放使用者的负担。
参考书目:
《Python CookBook》作者:【美】 David Beazley, Brian K. Jones
Github地址:
yidao620c/python3-cookbookgithub.com