问题 ctypes可变长度结构
自从我读到Dave Beazley关于二进制I / O处理的帖子(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解决方案?
12684
2017-08-10 17:55
起源
答案:
最简单的方法,您给出的示例是在您拥有所需信息时定义结构。
一个简单的方法是在你将使用它的时候创建类,而不是在模块根目录 - 你可以,例如,你只需要 class
一个函数内部的函数,它将充当工厂 - 我认为这是最可读的方式。
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代码将为您“填写”值 - 这不是答案。将以另一种方式发布。
10
2017-08-10 18:46
而现在,对于完全不同的东西 -
如果您只需要处理数据,那么“最Pythonic”方式可能不会尝试使用ctypes来处理内存中的原始数据。
这种方法只是使用struct.pack和.unpack来串行/取消序列化数据,因为它可以在你的app上移动。 “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)
2
2017-08-10 20:14
所以,就像在C中一样,你无法完全按照自己的意愿行事。
使用在C中执行所需操作的结构的唯一有用方法是将其作为
struct Points {
int num_points;
Point *points;
}
并有实用程序代码来分配您可以放置数据的内存。
除非你有一些安全的maxsize,并且不想打扰那部分代码
(内存分配) - 然后代码的网络部分将传输所需的内容
来自结构内部的数据,而不是整个数据。
使用具有结构成员的Python ctypes,该结构成员实际上包含指向数据所在位置的指针(因此,可能是可变长度) - 您还必须手动分配和释放内存(如果您在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', 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
使用全局变量“__all_buffers”保持对该引用的引用
python创建的缓冲区对象,以便python不会破坏它
离开make_points结构。另一种方法是获得参考
libc(在unixes上)或winapi,以及调用系统 malloc
和 free
功能你自己
或者 - 您可以使用普通的旧“struct”Python模块,而不是使用ctypes -
如果你根本没有C代码那么加倍,并且只是使用ctypes
“结构”方便。
2
2017-08-10 19:41
这是我到目前为止所提出的(仍然有点粗糙):
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
是通用的,因此可以将它们移动到元类。这将使它的使用几乎像我第一次发布的片段一样简单。
对此解决方案的评论?还在寻找更简单的东西,不确定它是否存在。
1
2017-08-11 21:17
这个问题真的很老了:
我有一个更简单的答案,这似乎很奇怪,但避免了元类,并解决了ctypes不允许我直接构建一个具有与我在C中相同的定义的结构的问题。
来自内核的示例C结构:
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>
这个答案对我有用,原因有两个:
- 构造函数的参数可以被pickle,并且没有对类型的引用。
- 我定义了StructwithVariableArrayLength定义中的所有结构。
- 对于调用者来说,结构看起来完全相同,好像我刚刚在_fields_中定义了数组一样
- 我无法修改头文件中定义的底层结构,并且无需更改任何底层代码即可实现我的目标。
- 我没有必要修改任何解析/包逻辑,这只是我正在尝试做的事情,即使用可变长度数组构建类定义。
- 这是一个通用的,可重复使用的容器,可以像我的其他结构一样发送到工厂。
我显然更喜欢头文件占用指针,但这并不总是可行的。那个答案令人沮丧。其他的非常适合数据结构本身,或者需要修改调用者。
1
2018-03-16 19:52