是否有一种优雅的方式来使用struct和namedtuple而不是这个?

时间:2012-07-12 21:41:05

标签: python struct binaryfiles namedtuple

我正在读一个由C中的记录组成的二进制文件,如下所示:

typedef _rec_t
{
  char text[20];
  unsigned char index[3];
} rec_t;

现在我能够将其解析为具有23个不同值的元组,但是如果我可以使用namedtuple将前20个字节合并到text并将剩下的3个字节合并到{ {1}}。我怎样才能做到这一点?基本上不是一个23个值的元组,而是我更喜欢分别有两个20和3个值的元组,并使用“自然名称”访问它们,即通过index

我目前正在使用namedtuple格式"20c3B"

注意:当我呼叫struct.unpack_from()时,字符串中有许多连续记录。


我的代码(剥离到相关部分):

parse_text

3 个答案:

答案 0 :(得分:6)

根据文档:http://docs.python.org/library/struct.html

  

可以通过将解包字段分配给变量或将结果包装到命名元组来命名解压缩字段:

>>> record = 'raymond   \x32\x12\x08\x01\x08'
>>> name, serialnum, school, gradelevel = unpack('<10sHHb', record)

>>> from collections import namedtuple
>>> Student = namedtuple('Student', 'name serialnum school gradelevel')
>>> Student._make(unpack('<10sHHb', record))
Student(name='raymond   ', serialnum=4658, school=264, gradelevel=8)

所以在你的情况下

>>> import struct
>>> from collections import namedtuple
>>> data = "1"*23
>>> fmt = "20c3B"
>>> Rec = namedtuple('Rec', 'text index') 
>>> r = Rec._make([struct.unpack_from(fmt, data)[0:20], struct.unpack_from(fmt, data)[20:]])
>>> r
Rec(text=('1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1'), index=(49, 49, 49))
>>>

切片解包变量可能是一个问题,如果格式是fmt = "20si"或标准我们不返回顺序字节的东西,我们就不需要这样做了。

>>> import struct
>>> from collections import namedtuple
>>> data = "1"*24
>>> fmt = "20si"
>>> Rec = namedtuple('Rec', 'text index') 
>>> r = Rec._make(struct.unpack_from(fmt, data))
>>> r
Rec(text='11111111111111111111', index=825307441)
>>>

答案 1 :(得分:2)

为什么不让parse_text使用字符串切片(data [:20],data [20:])来拉开这两个值,然后用struct处理每个值?

或者取23个值并将它们分成两个?

我一定错过了什么。也许您希望通过struct模块实现这一目标?

答案 2 :(得分:2)

这是我的答案。我首先使用切片而不是struct.unpack()来编写它,但@​​ samy.vilar指出我们可以使用“s”格式来实际获取字符串。 (我应该记得那个!)

这个答案使用struct.unpack()两次:一次取出字符串,一次将第二个字符串解包为整数。

我不确定你想对"3B"项做什么,但我猜你要把它解压缩为24位整数。我在3-char字符串的末尾添加了一个0字节并作为整数解压缩,以防万一你想要的。

有点棘手:像n, = struct.unpack(...)这样的行将一个长度为1的元组解压缩成一个变量。在Python中,逗号生成元组,因此在一个名称之后使用一个逗号我们使用元组解压缩将长度为1的元组解压缩为单个变量。

此外,我们可以使用with打开文件,从而无需try块。我们也可以使用f.read()一次读取整个文件,而无需计算文件的大小。

def parse_text(data):
    fmt = "20s3s"
    l = len(data)
    sz = struct.calcsize(fmt)

    if l % sz != 0:
        print("ERROR: input data not a multiple of record size")

    num_records = l / sz
    if not num_records:
        print "ERROR: zero-length input file."
        return

    ofs = 0
    while ofs < l:
        s, x = struct.unpack(fmt, data[ofs:ofs+sz])
        # x is a length-3 string; we can append a 0 byte and unpack as a 32-bit integer
        n, = struct.unpack(">I", chr(0) + x) # unpack 24-bit Big Endian int
        ofs += sz
        ... # do something with s and with n or x

def main():
    if len(sys.argv) != 2:
        print("Usage: program_name <input_file_name>")
        sys.exit(1)

    _, in_fname = sys.argv

    with open(in_fname) as f:
        data = f.read()
        parse_text(data)

if __name__ == "__main__":
    main()