Python ctypes против namedtuple

Итак, у меня есть две простые структуры ctypes

class S2 (ctypes.Structure):
    _fields_ = [
    ('A2',     ctypes.c_uint16*10),
    ('B2',     ctypes.c_uint32*10),
    ('C2',     ctypes.c_uint32*10) ]


class S1 (ctypes.Structure):
    _fields_ = [
    ('A',     ctypes.c_uint16),
    ('B',     ctypes.c_uint32),
    ('C',     S2) ]

Можно ли сделать то же самое, например, с namedtuple? Как обрабатываются списки в namedtuple?

Редактировать:

Использование struc.pack

test_data = '0100000002000000' + 10*'01' + 10*'01' + 10*'01'

S2 = collections.namedtuple('S2', ['A2', 'B2', 'C2'])
S1 = collections.namedtuple('S1', ['A', 'B', 'REF_to_S2'])

Data2 = S2._make(struct.unpack('10p10p10p', binascii.unhexlify(test_data[16:])))
##this is not working, because there must be 3 args..
Data1 = S1._make(struct.unpack('ii', binascii.unhexlify(test_data[0:16])))

В конце концов, я хочу распечатать данные в удобочитаемом формате (с видимыми парами ключ: значение). Но теперь я не могу понять, как мне справиться с этой операцией распаковки с двумя разными именованными кортежами...?

эта операция unpack.struct позаботится о проблемах с типом значения, верно?


person Juster    schedule 26.04.2013    source источник
comment
Что вы планируете делать с этими структурами? Вы передаете их функциям C или просто используете их в python? Кроме того, вам нужно гарантировать тип полей? Наконец, насколько я могу судить, объекты Structure изменяются, а namedtuple — нет.   -  person mgilson    schedule 26.04.2013
comment
Только внутри python, но я хотел бы быть уверен, что поля и полученные значения имеют один и тот же тип.   -  person Juster    schedule 27.04.2013
comment
Чтобы уточнить: в конечном итоге вы спрашиваете, как обрабатывать вложенные структуры в struct, верно? Вы не можете сделать это напрямую, но два наиболее очевидных способа сделать это косвенно (встраивание вложенных структур вручную в строки формата или манипулирование данными вручную) работают. Подробности смотрите в моем обновленном ответе. (И если это не то, о чем вы спрашиваете, извините.)   -  person abarnert    schedule 29.04.2013


Ответы (1)


Можно ли сделать то же самое, например, с namedtuple?

Это зависит от того, что вы подразумеваете под «то же самое». Вы можете легко создавать типы namedtuple с теми же полями:

S2 = collections.namedtuple('S2', ['A2', 'B2', 'C2'])
S1 = collections.namedtuple('S1', ['A', 'B', 'C'])

Однако они явно не одного типа и не будут иметь одинакового поведения.


Во-первых, эти поля являются обычными атрибутами Python (а также обычными членами tuple), что означает, что они не имеют статических типов; они могут содержать значения любого типа.

Итак, вы можете сделать это:

s2 = S2([ctypes.c_uint16(i) for i in range(10)],
        [ctypes.c_uint32(i) for i in range(10)],
        [ctypes.c_uint32(i) for i in range(10)])
s1 = S1(ctypes.c_uint16(1), ctypes.c_uint32(2), s2)

Но вы также можете сделать это:

s2 = S2('a', 'b', 'c')
s1 = S1('d', 'e', s2)

… или даже:

s1 = S1('d', 'e', 'f')

Также обратите внимание, что даже в первом примере фактически было создано lists из 10 ctypes значений, а не ctypes массивов. Если вы хотите этого, вы должны привести их явно.


Во-вторых, namedtuples являются расширением tuples, что означает, что они неизменяемы, поэтому вы не можете сделать это:

s1.C = s2

И самое главное, namedtuple нельзя использовать как ctypes.Structure — вы не можете передать его в функцию C, вам придется вручную писать логику (например, вокруг struct.pack), если вы хотите сериализовать его в каком-то конкретном двоичном формате, и т.п.


Как обрабатываются списки в namedtuple?

Как упоминалось выше, члены namedtuple не являются статически типизированными и могут содержать значения любого типа. Таким образом, они обрабатываются так же, как в tuple, list, обычном экземпляре класса, глобальной переменной и т. д. Просто вставьте туда list, и вы получите list.


да, этот stuct.pack - это то, что мне нужно. Но я не могу понять, как мне указать, что последнее значение в s1 является ссылкой на структуру s2.

Со стороны namedtuple вы просто используете экземпляр S2 в качестве значения для S1.C, как в моих примерах выше. Опять же, элементы/атрибуты namedtuple такие же, как и любые другие атрибуты/переменные/и т.д. в Python — просто имена, которые содержат ссылки на объекты. Таким образом, s1 = S1(1, 2, s2) превратит третий элемент s1 в другую ссылку на тот же объект, на который ссылается s2.

Что касается того, как использовать struct для сериализации данных: модуль struct не имеет возможности напрямую делегировать встроенному объекту. Но поскольку вывод pack — это всего лишь объект bytes (или, в Python 2.x, str), вы можете сделать это с помощью обычных манипуляций со строками:

# version 1
s2_struct = struct.Struct('!HII')
s1_header = struct.Struct('!HI')
def pack_s2(s2):
    return s2_struct.pack(s2.A2, s2.B2, s2.C2)
def unpack_s2(s2):
    return S2._make(s2_struct.unpack(s2))
def pack_s1(s1):
    return s1_header.pack(s1.A, s1.B) + pack_s2(s1.C)
def unpack_S1(s1):
    offset = len(s1_header)
    a, b = s1_header.unpack(s1[:offset])
    c = unpack_s2(s1[offset:])
    return S1._make(a, b, c)

(Лично я бы использовал S2(*struct.unpack, а не S2._make, но, поскольку в документации неоднократно используется последнее, я думаю, что это должен быть предполагаемый способ сделать что-то…)

Кроме того, вы можете сгладить строки формата вручную:

s2_struct = struct.Struct('!HII')
s1_struct = struct.Struct('!HIHII')
def pack_s2(s2):
    return s2_struct.pack(s2.A2, s2.B2, s2.C2)
def pack_s1(s1):
    return s1_struct.pack(s1.A, s1.B, s1.C.A2, s1.C.B2, s1.C.C2)
def unpack_s2(s2):
    return S2._make(s2_struct.unpack(s2))
def unpack_S1(s1):
    a, b, a2, b2, c2 = s1_struct.unpack(s1)
    c = S2(a2, b2, c2)
    return S1(a, b, c)

Я думаю, что вторую версию легче читать, но в ней также легче ошибиться, и она требует, чтобы вы думали о компоновке объектов на двоичном уровне, а не на уровне Python, поэтому... выбирайте меньшее из двух зол.

person abarnert    schedule 26.04.2013
comment
Я не знаю, считать ли этот ответ доблестным или безрассудным. Я имею в виду, что вопрос, вероятно, не заслуживает ответа, пока не будет предоставлена ​​​​информация, запрошенная комментарием @mgilson. - person John Y; 27.04.2013
comment
Спасибо, да, этот stuct.pack — это то, что мне нужно. Но я не могу понять, как мне указать, что последнее значение в s1 является ссылкой на структуру s2. - person Juster; 27.04.2013
comment
@Juster: Хорошо, я думаю, что понял, о чем вы спрашиваете, поэтому позвольте мне отредактировать ответ. - person abarnert; 29.04.2013
comment
Тот факт, что _make присутствует в примерах использования для namedtuple, не означает, что вы должны чувствовать необходимость использовать его вместо распаковки через *. Официальная партийная линия в отношении _make состоит в том, что она помогает избежать распаковки и переупаковки аргументов< /а>. Таким образом, возможно, _make дает небольшое преимущество в производительности, если аргументы уже объединены. Для меня это недостаточно убедительно, чтобы пересилить ваше чувство вкуса, и я совершенно уверен, что многие люди предпочитают использовать *. - person John Y; 29.04.2013
comment
@JohnY: Спасибо за ссылку! Вопрос в том, почему документы struct используют его. Очевидно, кто-то подумал, что это веская причина. Это не обязательно что-то доказывает, но… Я думаю, достаточно оставить обе возможности вместе с обсуждением? Или я слишком сильно наклоняюсь назад? - person abarnert; 30.04.2013
comment
Я думаю, что то, как вы справились с этим в этом ответе, в порядке. Что касается документов struct: они есть только один раз, в примере, который заполняет namedtuple из tuple, возвращаемых struct.unpack. Так что это могло быть сознательным решением поддержать _make, или это могло быть просто случай, когда я случайно ухватился за него первым. Я бы не стал слишком беспокоиться об этом в любом случае. - person John Y; 30.04.2013