2013-08-30 36 views
7

Tôi đang cố gắng để xử lý một định dạng nhị phân, làm theo tấm gương ở đây:dữ liệu nhị phân Parsing vào đối tượng ctypes Cấu trúc qua readinto()

http://dabeaz.blogspot.jp/2009/08/python-binary-io-handling.html

>>> from ctypes import * 
>>> class Point(Structure): 
>>>  _fields_ = [ ('x',c_double), ('y',c_double), ('z',c_double) ] 
>>> 
>>> g = open("foo","rb") # point structure data 
>>> q = Point() 
>>> g.readinto(q) 
24 
>>> q.x 
2.0 

tôi đã xác định một cấu trúc header của tôi và tôi đang cố gắng đọc dữ liệu vào cấu trúc của mình, nhưng tôi đang gặp một số khó khăn. cấu trúc của tôi là như thế này:

class BinaryHeader(BigEndianStructure): 
    _fields_ = [ 
       ("sequence_number_4bytes", c_uint), 
       ("ascii_text_32bytes", c_char), 
       ("timestamp_4bytes", c_uint), 
       ("more_funky_numbers_7bytes", c_uint, 56), 
       ("some_flags_1byte", c_byte), 
       ("other_flags_1byte", c_byte), 
       ("payload_length_2bytes", c_ushort), 

       ] 

Các ctypes documentation nói:

For integer type fields like c_int, a third optional item can be given. It must be a small positive integer defining the bit width of the field.

Vì vậy, đối ("more_funky_numbers_7bytes", c_uint, 56), Tôi đã cố gắng để xác định lĩnh vực này như một lĩnh vực 7 byte, nhưng tôi nhận được lỗi:

ValueError: number of bits invalid for bit field

Vì vậy, vấn đề đầu tiên của tôi là làm cách nào để xác định trường int 7 byte?

Sau đó, nếu tôi bỏ qua vấn đề đó và nhận xét trường "more_funky_numbers_7bytes", dữ liệu kết quả sẽ được tải trong .. nhưng như dự kiến ​​chỉ có 1 ký tự được tải vào "ascii_text_32bytes". Và vì lý do nào đó trả về 16 mà tôi giả định là số byte được tính toán mà nó đọc vào cấu trúc ... nhưng nếu tôi nhận xét trường "số sôi nổi" của tôi và "" ascii_text_32bytes "chỉ cho một char (1 byte) , không nên mà là 13, không phải 16 ???

Sau đó, tôi đã cố gắng phá vỡ lĩnh vực char thành một cấu trúc riêng biệt, và tham chiếu từ bên trong cấu trúc header của tôi. Nhưng điều đó không làm việc hoặc ...

class StupidStaticCharField(BigEndianStructure): 
    _fields_ = [ 
       ("ascii_text_1", c_byte), 
       ("ascii_text_2", c_byte), 
       ("ascii_text_3", c_byte), 
       ("ascii_text_4", c_byte), 
       ("ascii_text_5", c_byte), 
       ("ascii_text_6", c_byte), 
       ("ascii_text_7", c_byte), 
       ("ascii_text_8", c_byte), 
       ("ascii_text_9", c_byte), 
       ("ascii_text_10", c_byte), 
       ("ascii_text_11", c_byte), 
       . 
       . 
       . 
       ] 

class BinaryHeader(BigEndianStructure): 
    _fields_ = [ 
       ("sequence_number_4bytes", c_uint), 
       ("ascii_text_32bytes", StupidStaticCharField), 
       ("timestamp_4bytes", c_uint), 
       #("more_funky_numbers_7bytes", c_uint, 56), 
       ("some_flags_1byte", c_ushort), 
       ("other_flags_1byte", c_ushort), 
       ("payload_length_2bytes", c_ushort), 

       ] 

Vì vậy, bất kỳ ý tưởng làm thế nào để:

  1. Xác định một lĩnh vực 7 byte (mà I'l Tôi cần để giải mã sử dụng một chức năng được xác định)
  2. Xác định một lĩnh vực char tĩnh của 32 byte

CẬP NHẬT

Tôi đã tìm thấy một cấu trúc mà dường như làm việc ...

class BinaryHeader(BigEndianStructure): 
    _fields_ = [ 
       ("sequence_number_4bytes", c_uint), 
       ("ascii_text_32bytes", c_char * 32), 
       ("timestamp_4bytes", c_uint), 
       ("more_funky_numbers_7bytes", c_byte * 7), 
       ("some_flags_1byte", c_byte), 
       ("other_flags_1byte", c_byte), 
       ("payload_length_2bytes", c_ushort), 

       ] 

Bây giờ, câu hỏi còn lại của tôi là, tại sao khi sử dụng .readinto():

f = open(binaryfile, "rb") 

mystruct = BinaryHeader() 
f.readinto(mystruct) 

Máy quay trả lại 52 và không phải là mong đợi, 51. Đâu là byte phụ đến từ đâu, và nó đi đâu?

UPDATE 2 Đối với những người quan tâm đây là một example của một struct phương pháp khác để đọc các giá trị vào một namedtuple đề cập bởi eryksun:

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

Nếu bạn nhìn vào tệp nhị phân của mình bằng một số Hex Editor, bạn có thấy 51 byte không? Ngoài ra, 'len (mystruct) 'nói gì? –

+0

Có, 'binaryfile' là hơn 50KB. 'len (mystruct)' dường như không hoạt động, nhưng 'sizeof (mystruct)' trả về 52 ... – monkut

+1

Bạn có thể thêm '_pack_ = 1' vào định nghĩa, nhưng hãy xem xét sử dụng mô-đun' struct' với ' được đặt tên thay thế. – eryksun

Trả lời

5

định nghĩa dòng này thực sự là để xác định một bitfield:

... 
("more_funky_numbers_7bytes", c_uint, 56), 
... 

lỗi ở đây. Kích thước của một bitfield nên nhỏ hơn hoặc bằng với kích thước của các loại, vì vậy c_uint nên có ít nhất 32 tuổi, một chút bổ sung này sẽ nâng cao ngoại lệ:

ValueError: number of bits invalid for bit field 

Ví dụ về cách sử dụng bitfield:

from ctypes import * 

class MyStructure(Structure): 
    _fields_ = [ 
     # c_uint8 is 8 bits length 
     ('a', c_uint8, 4), # first 4 bits of `a` 
     ('b', c_uint8, 2), # next 2 bits of `a` 
     ('c', c_uint8, 2), # next 2 bits of `a` 
     ('d', c_uint8, 2), # since we are beyond the size of `a` 
          # new byte will be create and `d` will 
          # have the first two bits 
    ] 

mystruct = MyStructure() 

mystruct.a = 0b0000 
mystruct.b = 0b11 
mystruct.c = 0b00 
mystruct.d = 0b11 

v = c_uint16() 

# copy `mystruct` into `v`, I use Windows 
cdll.msvcrt.memcpy(byref(v), byref(mystruct), sizeof(v)) 

print sizeof(mystruct) # 2 bytes, so 6 bits are left floating, you may 
         # want to memset with zeros 
print bin(v.value)  # 0b1100110000 

những gì bạn cần là 7 byte vì vậy những gì bạn endup làm là đúng:

... 
("more_funky_numbers_7bytes", c_byte * 7), 
... 

đối với các kích thước cho cấu trúc, Nó sẽ là 52, tôi thêm byte sẽ được đệm đến align the structure trên 4 byte trên bộ xử lý 32 bit hoặc 8 byte trên 64 bit. Ở đây:

from ctypes import * 

class BinaryHeader(BigEndianStructure): 
    _fields_ = [ 
     ("sequence_number_4bytes", c_uint), 
     ("ascii_text_32bytes", c_char * 32), 
     ("timestamp_4bytes", c_uint), 
     ("more_funky_numbers_7bytes", c_byte * 7), 
     ("some_flags_1byte", c_byte), 
     ("other_flags_1byte", c_byte), 
     ("payload_length_2bytes", c_ushort), 
    ] 

mystruct = BinaryHeader(
    0x11111111, 
    '\x22' * 32, 
    0x33333333, 
    (c_byte * 7)(*([0x44] * 7)), 
    0x55, 
    0x66, 
    0x7777 
) 

print sizeof(mystruct) 

with open('data.txt', 'wb') as f: 
    f.write(mystruct) 

Byte thêm được đệm giữa other_flags_1bytepayload_length_2bytes trong file:

00000000 11 11 11 11 .... 
00000004 22 22 22 22 """" 
00000008 22 22 22 22 """" 
0000000C 22 22 22 22 """" 
00000010 22 22 22 22 """" 
00000014 22 22 22 22 """" 
00000018 22 22 22 22 """" 
0000001C 22 22 22 22 """" 
00000020 22 22 22 22 """" 
00000024 33 33 33 33 3333 
00000028 44 44 44 44 DDDD 
0000002C 44 44 44 55 DDDU 
00000030 66 00 77 77 f.ww 
      ^
     extra byte 

Đây là một vấn đề khi nói đến các định dạng tập tin và các giao thức mạng. Để thay đổi nó đóng gói nó bằng 1:

... 
class BinaryHeader(BigEndianStructure): 
    _pack_ = 1 
    _fields_ = [ 
     ("sequence_number_4bytes", c_uint), 
... 

file sẽ là:

00000000 11 11 11 11 .... 
00000004 22 22 22 22 """" 
00000008 22 22 22 22 """" 
0000000C 22 22 22 22 """" 
00000010 22 22 22 22 """" 
00000014 22 22 22 22 """" 
00000018 22 22 22 22 """" 
0000001C 22 22 22 22 """" 
00000020 22 22 22 22 """" 
00000024 33 33 33 33 3333 
00000028 44 44 44 44 DDDD 
0000002C 44 44 44 55 DDDU 
00000030 66 77 77 fww 

Đối với struct, nó sẽ không làm cho nó dễ dàng hơn trong trường hợp của bạn. Đáng buồn là nó không hỗ trợ các tập tin lồng nhau trong định dạng. Ví dụ ở đây:

>>> from struct import * 
>>> 
>>> data = '\x11\x11\x11\x11\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22 
\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x33 
\x33\x33\x33\x44\x44\x44\x44\x44\x44\x44\x55\x66\x77\x77' 
>>> 
>>> BinaryHeader = Struct('>I32cI7BBBH') 
>>> 
>>> BinaryHeader.unpack(data) 
(286331153, '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"' 
, '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"' 
, '"', '"', 858993459, 68, 68, 68, 68, 68, 68, 68, 85, 102, 30583) 
>>> 

Không thể sử dụng kết quả này namedtuple, bạn vẫn phân tích cú pháp dựa trên chỉ mục. Nó sẽ hoạt động nếu bạn có thể làm một cái gì đó như '>I(32c)(I)(7B)(B)(B)H'. Tính năng này đã được yêu cầu ở đây (Extend struct.unpack to produce nested tuples) từ năm 2003 nhưng không có gì được thực hiện kể từ đó.

+0

Cảm ơn bạn đã giải thích chi tiết! Bạn cũng quản lý để trả lời câu hỏi chưa được trả lời của tôi về "làm thế nào để tôi xử lý 2 lĩnh vực 4 bit mỗi?". Vì vậy, phương pháp ctypes đang tiến triển tốt. Tôi đã bị mắc kẹt với 'struct' cố gắng tìm ra cách xử lý trường hợp 4 bit. – monkut

+0

@monkut Tôi tin rằng 'struct' không hỗ trợ điều này, tất cả những gì nó suppors là các loại dữ liệu cơ bản. Bạn phải sử dụng [thao tác bitwise] (http://en.wikipedia.org/wiki/Bitwise_operation) và thực hiện thủ công. –

+0

Cảm ơn một lần nữa, tôi đã thực hiện nó và nó đã được cải thiện khoảng 60% so với mã sloppy trước đó của tôi. Vấn đề duy nhất là khi tôi đã cố gắng để đọc từ các tập tin tar ... dường như ExFileObject (đối tượng tập tin trả về bởi tarinfo) không hỗ trợ '.readinto (b)', doh! – monkut

Các vấn đề liên quan