Структуры переменной длины ctypes
С тех пор как я прочитал сообщение Dave Beazley по обработке двоичных операций ввода-вывода (http://dabeaz.blogspot.com/2009/08/python-binary-io-handling.html), я хотел создать библиотеку Python для определенного проводного протокола. Однако я не могу найти оптимальное решение для структур переменной длины. Вот что я хочу сделать:
import ctypes as c
class Point(c.Structure):
_fields_ = [
('x',c.c_double),
('y',c.c_double),
('z',c.c_double)
]
class Points(c.Structure):
_fields_ = [
('num_points', c.c_uint32),
('points', Point*num_points) # num_points not yet defined!
]
Класс Points
не будет работать, так как num_points
еще не определен. Я мог бы переопределить переменную _fields_
позже, как только num_points
будет известен, но поскольку это переменная класса, она будет влиять на все остальные экземпляры Points
.
Что такое pythonic решение этой проблемы?
Ответы
Ответ 1
Самый простой способ, с примером, который вы дали, - определить структуру, только когда у вас есть необходимая информация.
Простым способом создания этого класса является создание класса в том месте, где вы его используете, а не в корне модуля - вы можете, например, просто поместить тело class
внутри функции, которая будет действовать как factory - Я думаю, что это самый читаемый способ.
import ctypes as c
class Point(c.Structure):
_fields_ = [
('x',c.c_double),
('y',c.c_double),
('z',c.c_double)
]
def points_factory(num_points):
class Points(c.Structure):
_fields_ = [
('num_points', c.c_uint32),
('points', Point*num_points)
]
return Points
#and when you need it in the code:
Points = points_factory(5)
Извините -
Это код C, который "заполнит" значения для вас - это не ответ на них. WIll опубликует другой способ.
Ответ 2
Итак, как и в C, вы не можете делать то, что хотите.
Единственный полезный способ работы со структурой, которая делает то, что вы хотите на C, - иметь ее как
struct Points {
int num_points;
Point *points;
}
И у вас есть код утилиты для размещения вашей памяти, где вы можете поместить свои данные.
Если у вас нет безопасного maxsize и вы не хотите беспокоиться об этой части кода
(выделение памяти) - сетевая часть кода будет передавать только необходимые
данных изнутри структуры, а не всего.
Чтобы работать с типами Python со структурным членом, который фактически содержит указатель на то, где ваши данные (и, следовательно, может быть переменной длины), вам также придется выделять и освобождать память вручную (если вы ее заполняете сторона python) - или просто прочитать данные - f создание и уничтожение данных выполняется на основе собственных кодовых функций.
Код создания структуры может быть таким образом:
import ctypes as c
class Point(c.Structure):
_fields_ = [
('x',c.c_double),
('y',c.c_double),
('z',c.c_double)
]
class Points(c.Structure):
_fields_ = [
('num_points', c.c_uint32),
('points', c.POINTER(Point))
]
И код для управления созданием и удалением этих структур данных может быть:
__all_buffers = {}
def make_points(num_points):
data = Points()
data.num_points = num_points
buf = c.create_string_buffer(c.sizeof(Point) * num_points)
__all_buffers[c.addressof(buf)] = buf
p = Point.from_address(c.addressof(buf))
data.points = c.pointer(p)
return data
def del_points(points):
del __all_buffers[c.addressof(m.points[0])
points.num_points = 0
Использование f глобальная переменная "__all_buffers" содержит ссылку на
созданный python буферный объект, чтобы python не уничтожал его
оставляя структуру make_points. Альтернативой этому является получение ссылки на
либо libc (в unixes), либо winapi, а система вызовов malloc
и free
выполняет функции
ИЛИ - вы можете просто пойти с простым старым модулем "struct" Python, вместо использования ctypes -
вдвойне, если у вас вообще не будет кода на C, и просто используйте ctypes для
"structs" удобство.
Ответ 3
И теперь, для чего-то совершенно другого -
Если все, что вам нужно, связано с данными, возможно, "самый Pythonic" способ не пытается использовать ctypes для обработки необработанных данных в памяти вообще.
Этот подход просто использует struct.pack и .unpack для serialiase/unserialize данных, когда он перемещается в/из вашего приложения. Класс "Points" может принимать необработанные байты и создавать объекты python из этого и может сериализовать данные по методу "get_data". В противном случае это обычный список python.
import struct
class Point(object):
def __init__(self, x=0.0, y=0.0, z= 0.0):
self.x, self.y, self.z = x,y,z
def get_data(self):
return struct.pack("ddd", self.x, self.y, self.z)
class Points(list):
def __init__(self, data=None):
if data is None:
return
pointsize = struct.calcsize("ddd")
for index in xrange(struct.calcsize("i"), len(data) - struct.calcsize("i"), pointsize):
point_data = struct.unpack("ddd", data[index: index + pointsize])
self.append(Point(*point_data))
def get_data(self):
return struct.pack("i", len(self)) + "".join(p.get_data() for p in self)
Ответ 4
Вот что я до сих пор придумал (еще немного грубо):
import ctypes as c
MAX_PACKET_SIZE = 8*1024
MAX_SIZE = 10
class Points(c.Structure):
_fields_ = [
('_buffer', c.c_byte*MAX_PACKET_SIZE)
]
_inner_fields = [
('num_points', c.c_uint32),
('points', 'Point*self.num_points')
]
def __init__(self):
self.num_points = 0
self.points = [0,]*MAX_SIZE
def parse(self):
fields = []
for name, ctype in self._inner_fields:
if type(ctype) == str:
ctype = eval(ctype)
fields.append((name, ctype))
class Inner(c.Structure, PrettyPrinter):
_fields_ = fields
inner = Inner.from_address(c.addressof(self._buffer))
setattr(self, name, getattr(inner, name))
self = inner
return self
def pack(self):
fields = []
for name, ctype in self._inner_fields:
if type(ctype) == str:
ctype = eval(ctype)
fields.append((name, ctype))
class Inner(c.Structure, PrettyPrinter):
_fields_ = fields
inner = Inner()
for name, ctype in self._inner_fields:
value = getattr(self, name)
if type(value) == list:
l = getattr(inner, name)
for i in range(len(l)):
l[i] = getattr(self, name)[i]
else:
setattr(inner, name, value)
return inner
Методы parse
и pack
являются общими, поэтому их можно перенести в метакласс. Это сделало бы его почти таким же простым, как и первый фрагмент.
Комментарии к этому решению? Все еще ищете что-то более простое, не уверен, что оно существует.
Ответ 5
Этот вопрос действительно, действительно, старый:
У меня более простой ответ, который кажется странным, но избегает метаклассов и устраняет проблему, из-за которой ctypes не позволяет мне напрямую строить структуру с тем же определением, что и я в C.
Пример C struct, исходящий из ядра:
struct some_struct {
__u32 static;
__u64 another_static;
__u32 len;
__u8 data[0];
};
С реализацией ctypes:
import ctypes
import copy
class StructureVariableSized(ctypes.Structure):
_variable_sized_ = []
def __new__(self, variable_sized=(), **kwargs):
def name_builder(name, variable_sized):
for variable_sized_field_name, variable_size in variable_sized:
name += variable_sized_field_name.title() + '[{0}]'.format(variable_size)
return name
local_fields = copy.deepcopy(self._fields_)
for variable_sized_field_name, variable_size in variable_sized:
match_type = None
location = None
for matching_field_name, matching_type, matching_location in self._variable_sized_:
if variable_sized_field_name == matching_field_name:
match_type = matching_type
location = matching_location
break
if match_type is None:
raise Exception
local_fields.insert(location, (variable_sized_field_name, match_type*variable_size))
name = name_builder(self.__name__, variable_sized)
class BaseCtypesStruct(ctypes.Structure):
_fields_ = local_fields
_variable_sized_ = self._variable_sized_
classdef = BaseCtypesStruct
classdef.__name__ = name
return BaseCtypesStruct(**kwargs)
class StructwithVariableArrayLength(StructureVariableSized):
_fields_ = [
('static', ctypes.c_uint32),
('another_static', ctypes.c_uint64),
('len', ctypes.c_uint32),
]
_variable_sized_ = [
('data', ctypes.c_uint8)
]
struct_map = {
1: StructwithVariableArrayLength
}
sval32 = struct_map[1](variable_sized=(('data', 32),),)
print sval32
print sval32.data
sval128 = struct_map[1](variable_sized=(('data', 128),),)
print sval128
print sval128.data
С образцом вывода:
machine:~ user$ python svs.py
<__main__.StructwithVariableArrayLengthData[32] object at 0x10dae07a0>
<__main__.c_ubyte_Array_32 object at 0x10dae0830>
<__main__.StructwithVariableArrayLengthData[128] object at 0x10dae0830>
<__main__.c_ubyte_Array_128 object at 0x10dae08c0>
Этот ответ работает для меня по нескольким причинам:
- Аргумент конструктора может быть маринован и не имеет ссылок на типы.
- Я определяю всю структуру внутри определения StructwithVariableArrayLength.
- Для вызывающего, структура выглядит идентично, как если бы я только что определил массив внутри _fields _
- У меня нет возможности модифицировать базовую структуру, определенную в файле заголовка, и выполнить мои цели, не изменяя базовый код.
- Мне не нужно изменять какую-либо логику parse/pack, это только делает то, что я пытаюсь сделать, что создает определение класса с массивом переменной длины.
- Это универсальный контейнер многократного использования, который отправляется в factory, как и мои другие структуры.
Я бы предпочел, чтобы заголовочный файл взял указатель, но это не всегда возможно. Этот ответ был разочаровывающим. Другие были очень адаптированы к самой структуре данных или потребовали модификации вызывающего абонента.