2013-08-30 16 views
7

próbuję obsłużyć format binarny, wzorując się tutaj:Parsing danych binarnych na obiekcie Structure ctypes przez 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 

mam zdefiniowanej struktury mojego nagłówka i próbuję odczytać dane w mojej strukturze, ale mam pewne trudności. Moja struktura jest tak:

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

       ] 

ctypes documentation mówi:

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.

Więc dla ("more_funky_numbers_7bytes", c_uint, 56), Próbowałem zdefiniować boiska w polu 7 bajtów, ale dostaję błąd:

ValueError: number of bits invalid for bit field

Więc moim pierwszym problemem jest jak zdefiniować 7-bajtowe pole int?

Następnie Jeśli pominę ten problem i skomentuję pole "more_funky_numbers_7bytes", wynikowe dane zostaną wczytane .. ale zgodnie z oczekiwaniami tylko 1 znak zostanie załadowany do "ascii_text_32bytes". I z jakiegoś powodu zwraca 16, który zakładam, jest obliczoną liczbą bajtów, które odczytał w strukturze ... ale jeśli skomentuję moje pole "numer funky" i "" ascii_text_32bytes "to tylko jeden znak (1 bajt) nie powinno to być 13, a nie 16 ???

Potem próbowałem wyłamaniu pola char do osobnej struktury i odniesienia, który od wewnątrz mojego struktury nagłówka. Ale to nie działa albo ...

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

       ] 

Więc jakieś pomysły jak:

  1. zdefiniować pole 7 bajtów (które I'l Muszę do dekodowania przy użyciu określonej funkcji)
  2. Definiowanie statycznego pola char 32 bajtów

UPDATE

Znalazłem strukturę, która wydaje się działać ...

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

       ] 

Teraz pozostaje moje pytanie, dlaczego podczas używania .readinto():

f = open(binaryfile, "rb") 

mystruct = BinaryHeader() 
f.readinto(mystruct) 

Powraca 52, a nie oczekuje, 51. Skąd pochodzi ten dodatkowy bajt i dokąd zmierza?

UPDATE 2 Dla zainteresowanych oto example alternatywnego struct metody do odczytu wartości do namedtuple wspomnianym przez 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

Jeśli spojrzysz na plik binarny za pomocą edytora heksadecymalnego, czy widzisz 51 bajtów? Co mówi "len (mystruct)"? –

+0

Tak, plik 'binaryfile' ma ponad 50 KB. 'len (mystruct)' nie wydaje się działać, ale 'sizeof (mystruct)' zwraca 52 ... – monkut

+1

Możesz dodać '_pack_ = 1' do definicji, ale rozważ użycie modułu' struct' z '' namedtuple' zamiast tego. – eryksun

Odpowiedz

5

definicja ta linia jest faktycznie do definiowania bitfield:

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

co jest tutaj nieprawidłowe. Wielkość bitowym powinna być mniejsza niż lub równa wielkości w rodzaju tak c_uint powinien wynosić co najwyżej 32, jeden dodatkowy bit podniesie wyjątek:

ValueError: number of bits invalid for bit field 

przykład zastosowania bitowym:

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 

co potrzebne jest 7 bajtów więc co endup robi jest poprawne:

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

co do wielkości na strukturze, to będzie 52, to dodatkowy bajt zostanie wypełniona do align the structure na 4 bajtach na procesorze 32-bitowym lub 8-bajtowym na 64-bitowym. Tutaj:

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) 

Dodatkowy bajt napawa między other_flags_1byte i payload_length_2bytes w pliku:

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 

Jest to problem, jeśli chodzi o tych formatów i protokołów sieciowych. Aby zmienić to pakować go 1:

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

plik będzie:

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 

chodzi o struct, nie będzie łatwiej w Twoim przypadku. Niestety nie obsługuje zagnieżdżonych krotek w tym formacie. Na przykład tutaj:

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

Wynik ten nie może być stosowany namedtuple, trzeba jeszcze przeanalizować go na podstawie indeksu. To zadziała, jeśli możesz zrobić coś takiego, jak '>I(32c)(I)(7B)(B)(B)H'. Ta funkcja jest wymagana tutaj od 2003 roku, ale od tego czasu nic się nie dzieje.

+0

Dzięki za szczegółowe wyjaśnienie! Udało Ci się również odpowiedzieć na moje bez odpowiedzi pytanie: "jak mam obsłużyć 2 pola po 4 bity?". Metoda ctypes postępuje więc dobrze. Utknąłem z 'struct' próbując dowiedzieć się jak poradzić sobie z 4-bitowym przypadkiem. – monkut

+0

@monkut Wierzę, że 'struct' nie obsługuje tego, wszystko co to supporty są podstawowymi typami danych. Musisz użyć [operacji bitowej] (http://en.wikipedia.org/wiki/Bitwise_operation) i zrobić to ręcznie. –

+0

Jeszcze raz dziękuję, zaimplementowałem to i było około 60% poprawy w stosunku do mojego poprzedniego niechlujnego kodu. Jedynym problemem jest to, kiedy poszedłem próbować odczytać z plików tar ... najwyraźniej ExFileObject (obiekty plików zwrócone przez tarinfo) nie obsługują '.readinto (b)', doh! – monkut

Powiązane problemy