有没有一种优雅的方式来使用struct和namedtuple来代替这个?

问题描述 投票:0回答:4

我正在读取一个由记录组成的二进制文件,在 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()
python struct binaryfiles namedtuple
4个回答
9
投票

根据文档: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)
>>>

4
投票

这是我的答案。 我首先使用切片而不是

struct.unpack()
来编写它,但 @samy.vilar 指出我们可以使用“s”格式来实际获取字符串。 (我应该记住这一点!)

这个答案使用

struct.unpack()
两次:一次将字符串取出,一次将第二个字符串解压为整数。

我不确定您想对

"3B"
项做什么,但我猜您想将其解压为 24 位整数。 我在 3 个字符的字符串末尾附加了一个 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()

3
投票

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

或者将 23 个值分成两部分?

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


0
投票

这是

Struct
的子类,它可以从任何序列打包并解包到您选择的类:

class ObjectStruct(Struct):
    def __init__(self, *args, object_cls=tuple, **kwargs):
        super().__init__(*args, **kwargs)
        self._object_cls = object_cls
    def pack(self, object):
        return super().pack(*object)
    def pack_into(self, buffer, offset, object):
        return super().pack_into(buffer, offset, *object)
    def unpack(self, *args, **kwargs):
        return self._object_cls(*super().unpack(*args, **kwargs))
    def unpack_from(self, *args, **kwargs):
        return self._object_cls(*super().unpack_from(*args, **kwargs))
    def iter_unpack(self, *args, **kwargs):
        for item in super().iter_unpack(*args, **kwargs):
            yield self._object_cls(*item)

以下是如何将其与

namedtuple
类一起使用:

from collections import namedtuple
WAISHeader = namedtuple("WAISHeader", "msg_len msg_type hdr_vers server compression encoding msg_checksum")
WAISHeaderStruct = ObjectStruct("! 10s c c 10s c c c", object_cls=WAISHeader)
headbytes = b"0000000142z2wais        0"
header = WAISHeaderStruct.unpack(headbytes)
headbytes2 = WAISHeaderStruct.pack(header)

演示:

Python 3.12.3 (main, Sep 11 2024, 14:17:37) [GCC 13.2.0]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.20.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: from collections import namedtuple
   ...: WAISHeader = namedtuple("WAISHeader", "msg_len msg_type hdr_vers server compression encoding msg_checksum")
   ...: WAISHeaderStruct = ObjectStruct("! 10s c c 10s c c c", object_cls=WAISHeader)
   ...: headbytes = b"0000000142z2wais        0"
   ...: header = WAISHeaderStruct.unpack(headbytes)
   ...: headbytes2 = WAISHeaderStruct.pack(header)
   ...:

In [2]: headbytes
Out[2]: b'0000000142z2wais        0'

In [3]: header
Out[3]: WAISHeader(msg_len=b'0000000142', msg_type=b'z', hdr_vers=b'2', server=b'wais      ', compression=b' ', encoding=b' ', msg_checksum=b'0')

In [4]: headbytes2
Out[4]: b'0000000142z2wais        0'

© www.soinside.com 2019 - 2024. All rights reserved.