Вложение структуры в структуру struct
Вот к примеру такая стуктура в псевдосишном виде
struct {
uint32 b,
struct {
int8 x,
int8 y
} xy,
uint16 a[3]
}
В питоне такая штука подходит для статичной структуры
>>> struct.unpack('<Ibb3H', b'123411121212')
(875770417, 49, 49, 12849, 12849, 12849)
Хочется чтоб оно выдавало в идеале такое
>>> struct.unpack('<I(bb)(H)[3]', b'123411121212')
(875770417, (49, 49), [12849, 12849, 12849])
в struct такого нет, в numpy не нахожу...
Для реального примера формат структуры
FORMATS = [
'I', 'H', 'I', 'B', 'B', 'B', 'B', 'B',
'I', 'i', 'i', 'i', 'f', 'H', 'f', 'f',
'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H',
'H', 'H', 'H', 'H', 'B', 'B', 'B', 'B',
'I', 'I', 'H', 'H', 'I', 'H', 'H', 'H',
'H', 'H', 'H', 'H', 'b', 'b', 'b', 'b',
'b', 'b', 'b', 'b', 'H', 'f', 'H', 'b',
'f', 'H', 'H', 'H', 'H', 'H', 'B', 'B',
'B', 'H', 'I', 'h', 'B', '8c', 'BB', 'B',
'QQ', 'i', 'H', 'f', 'IHHHbIHHHbIHHHbI', 'b', 'b', 'b',
'b', 'b', 'b', 'Hb', 'Hb', 'Hb', 'Hb', 'Hb',
'Hb', 'Hb', 'Hb', 'Hb', 'Hb', 'BBbBBb', 'BBbBBbBBbBBb', 'BBbBBbBBbBBbBBbBBbBBbBBb',
'BBbBBbBBbBBbBBbBBbBBbBBbBBbBBbBBbBBbBBbBBbBBbBBb', 'B', 'B', 'H', 'B', 'I', 'I', 'B',
'I', 'H', 'hhh', 'H', 'hhh', 'BB', 'BB', 'BB',
'BB', 'BB', 'BB', 'BB', 'BB', 'B', 'H', 'H',
'H', 'B', 'B', 'B', 'B', 'B', 'I', 'I',
'I', 'I', 'I', 'I', 'H', 'H', 'H', 'H',
'H', 'H', 'B', 'B', 'bb', 'bbb', 'h', 'B',
'B', 'BBB', 'H', 'H', 'H', 'H', 'H', 'H',
'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H',
'H', 'H', 'h', 'h', 'h', 'h', 'B', 'B',
'B', 'B', 'H', 'I', 'I', 'I', 'h', 'I',
'h', 'h', 'I', 'h', 'h', 'BB', 'h', 'h',
'h', 'h', 'h', 'h', 'h', 'h', 'H', 'H',
'I', 'I', 'HH', 'HH', 'HHH', 'BH', 'B', 'H',
'H', 'B', 'I', 'BI', 'I', 'I', '1s', '1s',
'1s', '1s', '1s', '1s', '1s', '1s', '1s', '1s',
'1s', '1s', '1s', '1s', '1s', '1s', '2s', '2s',
'2s', '2s', '2s', '2s', '2s', '2s', '2s', '2s',
'2s', '2s', '2s', '2s', '2s', '4s', '4s', '4s',
'4s', '4s', '4s', '4s', '4s', '4s', '4s', '4s',
'4s', '4s', '4s', '4s', '8s', '8s', '8s'
]
В пакете передаются обычно штук 10 параметров из этих, остальное пропускается.
Пример данных:
маска fbee30082e080000000e00000000000000000000000000000000000000000000
структура 010000000000113bb1680020630c113bb16830c45501f0a8fb01000000000000000000000000000000000000000000320064008003000000090012000400dd05000000060009000400dd08000000040007000800dd113bb16814281b
содержимое которое сейчас получаю
{
1: (1,),
2: (0,),
3: (1756445457,),
4: (0,),
5: (32,),
7: (99,),
8: (12,),
9: (1756445457,),
10: (22398000,),
11: (33270000,),
13: (0.0,),
14: (0,),
15: (0.0,),
19: (0,),
20: (0,),
29: (0,),
35: (0,),
37: (0,),
38: (50,),
39: (100,),
45: (-128,),
77: (3, 9, 18, 4, -35, 5, 6, 9, 4, -35, 8, 4, 7, 8, -35, 1756445457),
78: (20,),
79: (40,)
}
хочу получать:
{
1: 1 ,
2: 0 ,
3: 1756445457 ,
....
39: 100 ,
45: -128 ,
77: ([(3, 9, 18, 4, -35), (5, 6, 9, 4, -35), (8, 4, 7, 8, -35)], 1756445457),
78: 20 ,
79: 40
}
Но вопрос именно как делать структуру в структуре.
Ответы (2 шт):
Как использовать numpy
для работы со вложенными структурами
Исходная структура в Си:
typedef struct {
unsigned int b;
struct {
signed char x;
signed char y;
} xy;
unsigned short a[3];
} MyStruct;
Эта же структура в NumPy:
MyStruct = np.dtype([
('b', '<I'),
('xy', [
('x', 'b'),
('y', 'b')
]),
('a', '<3H')
])
data = np.void(b'123411121212').view(dtype=MyStruct, type=np.recarray)
print(f'{data = !s}',
f'{data.b = !s}',
f'{data.xy.x = !s}',
f'{data.xy.y = !s}',
f'{data.a = !s}',
sep='\n')
Результат:
data = (875770417, (49, 49), [12849, 12849, 12849])
data.b = 875770417
data.xy.x = 49
data.xy.y = 49
data.a = [12849 12849 12849]
Здесь:
- numpy.void - приемник для последовательности байт как скалярной величины;
- numpy.ndarray.view - интерпретация содержимого через новый тип данных;
- numpy.dtype - создание нового типа данных;
- numpy.recarray - апгрейд
numpy.ndarray
, в котором доступ к полям структуры возможен по их имени через точку вместо квадратных скобок:data.field_name
vs.data['field_name']
.
Если какое-то поле нужно описать как массив, то его размерность задаётся третьим параметром кортежа. Пример:
# Поле по имени 'my_field' содержит массив 15×15×3 целых чисел без знака
np.dtype([('my_field', '<I', (15, 15, 3)), ...])
Доступ к ячейкам массива тот же, что и к ячейкам numpy.ndarray
- через индекс после обращения к полю: data['my_field'][0, :, 1]
. Но совмещать в одном обращении имя поля и индексы ячеек нельзя - обращение data['my_field', 0, :, 1]
выбросит ошибку IndexError
.
Относительно простые структуры с вложениями однотипных массивов можно записать в одну строку. Для полей автоматически создаются имена вида 'f0', 'f1', 'f2', ...
. Пример:
# field 0: unsigned integer, 4 bytes
# field 1: 2×3 array of signed chars, 6 bytes
# field 2: 3 unsigned shorts, 6 bytes
my_type = np.dtype('<I, (2, 3)b, <3H')
raw_data = np.void(b'\xFF\x00\x00\x00\x01\x02\x03\x04\x05\x06\x0A\x00\x0B\x00\x0C\x00')
data = raw_data.view(dtype = my_type, type=np.recarray)
print('Содержимое:',
f'{data = !s}',
f'{data.f0 = !s}',
f'data.f1 =\n{data.f1}',
f'{data.f2 = !s}',
'\nПримеры доступа к ячейкам:',
f'{data[1][0, 0] = !s}',
f'{data.f1[0, 0] = !s}', # numpy.recarray
f"{data['f1'][:, 1] = !s}",
f"{data['f1'][0, 0] = !s}",
f'{data[2][0] = !s}',
f'{data.f2[0] = !s}', # numpy.recarray
f"{data['f2'][0] = !s}",
sep='\n')
Содержимое:
data = (255, [[1, 2, 3], [4, 5, 6]], [10, 11, 12])
data.f0 = 255
data.f1 =
[[1 2 3]
[4 5 6]]
data.f2 = [10 11 12]
Примеры доступа к ячейкам:
data[1][0, 0] = 1
data.f1[0, 0] = 1
data['f1'][:, 1] = [2 5]
data['f1'][0, 0] = 1
data[2][0] = 10
data.f2[0] = 10
data['f2'][0] = 10
Документация
Пример работы с динамически изменяемыми структурами
Задан фиксированный список из 255 форматов и маска из 32 байт (используются первые 255 бит). По маске выбираются используемые форматы. Отобранные форматы применяются в той последовательности, как они встречаются в списке, ко входной последовательности байт.
На выходе нужно получить словарь, ключи которого - это положения форматов в списке (индекс + 1), а значения - извлеченные по ним данные.
Для решения задачи используем NumPy. Первым делом, перепишем сложные форматы в понятном для NumPy виде. Например, "bbb"
может быть "b,b,b"
(три отдельных байтовых поля), "3b"
(одно поле с тремя байтовыми ячейками), "b,2b"
, "2b,b"
. Также нужно учесть, что символы форматов в struct, используемые в условии задачи, не во всём совпадают с numpy. Например:
- малую
's'
изstruct
понадобится перевести в большую'S'
изnumpy
; - длина целого
'l', 'L'
равна 4 байтам вstruct
и 8 вnumpy
, их нужно заменить на'i', 'I'
- целое число из 4 байт; - символы выравнивания и порядка
'@!'
и'x'
(pad byte) неизвестны в NumPy; - символ порядка байтов (endianness) в
struct
указывается один раз в начале формата, а вnumpy
- для каждого поля, где это имеет смысл.
Для текущего примера актуально только первое замечание, остальные можем проигнорировать.
Вложенные структуры придётся развернуть детальнее. В примере из вопроса возьмем структуру FORMATS[76]
, равную 'IHHHbIHHHbIHHHbI'
. По ней ожидается результат вида:
([(3, 9, 18, 4, -35), (5, 6, 9, 4, -35), (8, 4, 7, 8, -35)], 1756445457)
Чтобы получить такой результат с помощью NumPy, структуру нужно переписать так:
[('', 'I,H,H,H,b', (3,)), ('', 'I')]
Здесь имена полей не важны, поэтому оставляем их в виде пустой строки - им автоматически подберутся имена 'f0', 'f1', 'f2', ...
.
Минимальное преобразование форматов из примера может выглядеть так:
# Исходные форматы структур данных
formats = ['I', 'H', 'I', 'B', 'B', 'B', 'B', 'B', 'I', 'i', 'i', 'i', 'f', 'H', 'f', 'f', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'B', 'B', 'B', 'B', 'I', 'I', 'H', 'H', 'I', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'H', 'f', 'H', 'b', 'f', 'H', 'H', 'H', 'H', 'H', 'B', 'B', 'B', 'H', 'I', 'h', 'B', '8c', 'BB', 'B', 'QQ', 'i', 'H', 'f', 'IHHHbIHHHbIHHHbI', 'b', 'b', 'b', 'b', 'b', 'b', 'Hb', 'Hb', 'Hb', 'Hb', 'Hb', 'Hb', 'Hb', 'Hb', 'Hb', 'Hb', 'BBbBBb', 'BBbBBbBBbBBb', 'BBbBBbBBbBBbBBbBBbBBbBBb', 'BBbBBbBBbBBbBBbBBbBBbBBbBBbBBbBBbBBbBBbBBbBBbBBb', 'B', 'B', 'H', 'B', 'I', 'I', 'B', 'I', 'H', 'hhh', 'H', 'hhh', 'BB', 'BB', 'BB', 'BB', 'BB', 'BB', 'BB', 'BB', 'B', 'H', 'H', 'H', 'B', 'B', 'B', 'B', 'B', 'I', 'I', 'I', 'I', 'I', 'I', 'H', 'H', 'H', 'H', 'H', 'H', 'B', 'B', 'bb', 'bbb', 'h', 'B', 'B', 'BBB', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'h', 'h', 'h', 'h', 'B', 'B', 'B', 'B', 'H', 'I', 'I', 'I', 'h', 'I', 'h', 'h', 'I', 'h', 'h', 'BB', 'h', 'h', 'h', 'h', 'h', 'h', 'h', 'h', 'H', 'H', 'I', 'I', 'HH', 'HH', 'HHH', 'BH', 'B', 'H', 'H', 'B', 'I', 'BI', 'I', 'I', '1s', '1s', '1s', '1s', '1s', '1s', '1s', '1s', '1s', '1s', '1s', '1s', '1s', '1s', '1s', '1s', '2s', '2s', '2s', '2s', '2s', '2s', '2s', '2s', '2s', '2s', '2s', '2s', '2s', '2s', '2s', '4s', '4s', '4s', '4s', '4s', '4s', '4s', '4s', '4s', '4s', '4s', '4s', '4s', '4s', '4s', '8s', '8s', '8s']
# Заменить проблемные символы
formats = [f.replace('s', 'S') for f in formats]
# Разделить поля запятой, чтобы форматы были применимы в numpy
# (ситуативное решение за неимением примеров ожидаемого результата)
formats = [','.join(re.findall(r'[0-9]*[a-zA-Z]', f)) for f in formats]
# Переделать формат №77 по заданному примеру
formats[76] = [('', 'I,H,H,H,b', (3,)), ('', 'I')]
# Сохранить позицию формата как имя будущего поля
# (используем при создании конечного словаря)
formats = [(str(i), f) for i, f in enumerate(formats, start=1)]
Следующим шагом формируем по заданной маске структуру входных данных:
hex_mask = 'fbee30082e080000000e00000000000000000000000000000000000000000000'
mask = int(hex_mask, base=16)
selected_formats_indices = [i for i in range(len(formats)) if (mask & 2**(255-i))]
selected_formats = [formats[i] for i in selected_formats_indices]
data_type = np.dtype(selected_formats)
Принимаем входные данные и проверяем применимость к ним созданной структуры:
# Последний байт данных закомментирован как служебный (см. комментарий к ответу)
hex_data = '010000000000113bb1680020630c113bb16830c45501f0a8fb01000000000000000000000000000000000000000000320064008003000000090012000400dd05000000060009000400dd08000000040007000800dd113bb1681428' # '1b'
raw_data = np.void(bytes.fromhex(hex_data))
assert data_type.itemsize == raw_data.itemsize, f"Can't apply type of size {data_type.itemsize} to data of size {data.itemsize}"
Может понадобиться пройтись вглубь данных, чтобы свести элементы вложенных структур к пайтоновским типам:
def to_python(npdata):
if isinstance(npdata, (np.ndarray, np.generic)):
data = npdata.tolist()
# если тип данных не `void`, то вложенных структур нет
return data if npdata.dtype.char != 'V' else to_python(data)
if isinstance(npdata, (list, tuple)):
# `tolist` возвращает `tuple` на скалярных величинах
# сложной структуры и `list` на массивах; проверяем их
# на наличие более глубоких вложенных структур
return type(npdata)(map(to_python, npdata))
return npdata
Конечное преобразование:
data = raw_data.view(data_type)
keys = list(map(int, data_type.names)) # номера форматов в именах полей
values = to_python(data) # приводим данные к пайтоновским типам
answer = dict(zip(keys, values))
print(answer)
{1: 1,
2: 0,
3: 1756445457,
4: 0,
5: 32,
7: 99,
8: 12,
9: 1756445457,
10: 22398000,
11: 33270000,
13: 0.0,
14: 0,
15: 0.0,
19: 0,
20: 0,
29: 0,
35: 0,
37: 0,
38: 50,
39: 100,
45: -128,
77: ([(3, 9, 18, 4, -35), (5, 6, 9, 4, -35), (8, 4, 7, 8, -35)], 1756445457),
78: 20,
79: 40}