我正在读取一个由记录组成的二进制文件,在 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()
根据文档: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)
>>>
这是我的答案。 我首先使用切片而不是
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()
为什么不让 parse_text 使用字符串切片 (data[:20], data[20:]) 来分离两个值,然后用 struct 处理每个值?
或者将 23 个值分成两部分?
我一定是错过了什么。 也许您希望通过 struct 模块来实现这一点?
这是
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'