2012-07-12 67 views
3

我读由记录的二进制文件,在C应该是这样的:有没有一种优雅的方式来使用struct和namedtuple而不是这个?

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

现在我能够解析与23个不同值的元组里面,但我们更希望,如果我能使用namedtuple将前20个字节组合到text中,并将其余三个字节组合到index中。我怎样才能做到这一点?基本上而不是23个值的一个元组,我倾向于分别具有两个20和3值的元组,并使用“自然名称”(即通过namedtuple)访问这些元组。

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

注意:当我拨打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() 
+0

'data = str()'(在'main'中)是不必要的。 – mgilson 2012-07-12 21:57:08

+0

@mgilson:谢谢:) – 0xC0000022L 2012-07-12 22:11:48

回答

5

根据文档:

>>> 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) 

所以你的情况

http://docs.python.org/library/struct.html

解压后场可以将它们分配给变量的或由一个名为元组包裹,结果被命名为

>>> 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) 
>>> 
+0

Aaah,请参阅......'_make'调用内切片的创造性使用是我错过的。谢谢一堆。我一直在跟踪文档,但没有得到如何做到这一点没有额外的变量。我会尽快接受,但不能upvote,直到午夜后(用完所有选票:))。 – 0xC0000022L 2012-07-12 22:20:58

+0

@ 0xC0000022L没问题,虽然由于开销太大而难以分割,但是我对你的情况了解不多,这是我能想到的最好的情况。 – 2012-07-12 22:25:45

+2

我们真的打算使用'_make'吗?这个领先的下划线是可疑的... – 2012-07-12 23:12:47

2

为什么不parse_Text在使用字符串的切片(数据[20],数据[20:])拉开两值,然后用struct来处理每一个?

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

我必须缺少一些东西。也许你希望通过结构模块来实现这一点?

+0

+1。字符串数据就在'data'中,并且使用切片去掉两个固定长度的字符串是微不足道的;不需要使用'struct.unpack_from()'从字符串中获取字符串。 'struct'对于解压整数值和浮点值非常有价值,但在这里我们只有字符串。 – steveha 2012-07-12 22:01:44

+0

@steveha:即使我想使用'offset',我不需要'unpack_from'?请记住,这是我缩小的一个例子。 – 0xC0000022L 2012-07-12 22:13:12

+0

@ user1277476:该字符串包含'num'记录(请参阅代码)。这就是为什么我首先使用'unpack_from'的原因。当然切片也可以。谢谢。 – 0xC0000022L 2012-07-12 22:14:59

2

这是我的答案。我首先使用分片而不是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() 
+0

谢谢,这个答案有一些概念,我在书中学习(学习Python ),但还没有太多使用我自己。 – 0xC0000022L 2012-07-13 12:13:26

+0

再次感谢。这个解决方案非常出色,特别是解码整数的部分。感谢那。由于我的问题不同(部分原因是缺乏知识),我认为这是不公平的,接受这个答案。但是,我积极筛选了一些其他与Python相关的答案,并花费了我的每日投票限额。再次感谢! – 0xC0000022L 2012-07-13 14:50:28

+0

不客气! :-) – steveha 2012-07-13 18:49:16

相关问题