7

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

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

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

我目前使用的格式"20c3B"struct.unpack_from().

注意:当我调用时,字符串中有很多连续的记录parse_text


我的代码(精简到相关部分):

#!/usr/bin/env python
import sys
import os
import struct
from collections import namedtuple

def parse_text(data):
    fmt = "20c3B"
    l = len(data)
    sz = struct.calcsize(fmt)
    num = l/sz
    if not num:
        print "ERROR: no records found."
        return
    print "Size of record %d - number %d" % (sz, num)
    #rec = namedtuple('rec', 'text index')
    empty = struct.unpack_from(fmt, data)
    # Loop through elements
    # ...

def main():
    if len(sys.argv) < 2:
        print "ERROR: need to give file with texts as argument."
        sys.exit(1)
    s = os.path.getsize(sys.argv[1])
    f = open(sys.argv[1])
    try:
        data = f.read(s)
        parse_text(data)
    finally:
        f.close()

if __name__ == "__main__":
    main()
4

3 回答 3

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)
>>>
于 2012-07-12T22:17:48.363 回答
3

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

还是取 23 个值并将它们一分为二?

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

于 2012-07-12T21:55:49.957 回答
3

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

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

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

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

另外,我们可以使用 awith打开文件,这样就不需要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()
于 2012-07-12T22:34:01.167 回答