Python ctypes可变长度结构

Python ctypes可变长度结构,python,python-3.x,ctypes,Python,Python 3.x,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',

自从我读了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!
        ]
由于尚未定义
num\u Points
,因此类
Points
将不起作用。我可以在知道
num\u points
后重新定义
\u fields
变量,但由于它是一个类变量,因此会影响所有其他
实例


这个问题的pythonic解决方案是什么?

对于您给出的示例,最简单的方法是在您拥有所需信息时定义结构

一种简单的方法是在您将要使用的位置创建类,而不是在模块根位置创建类-例如,您可以将
主体放在函数中,作为工厂-我认为这是最可读的方法

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代码将为您“填充”这些值-这不是它们的答案。将以另一种方式发布。

因此,就像在C中一样,您不能完全按照自己的意愿进行操作。 使用在C中实现所需功能的结构的唯一有用方法是将其作为

结构点{
积分;
点*点;
}
并有实用程序代码来分配你的内存,你可以把你的数据。 除非您有一些安全的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 
全局变量“\uu all\u buffers”的使用保留了对 python创建了缓冲区对象,以便python不会在 离开make_points结构。另一种方法是获取对 libc(在unix上)或winapi,并自己调用系统的
malloc
free
函数

或者-您可以使用普通的老“struct”Python模块,而不是使用ctypes- 如果您根本没有C代码,并且只使用ctypes作为
“结构”方便。

现在,为了完全不同的东西- 如果您只需要处理数据,那么“最具python风格”的方法可能是根本不尝试使用ctypes来处理内存中的原始数据

此方法仅使用struct.pack和.unpack在数据在应用程序上/下移动时序列化/取消序列化数据。“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)

以下是我到目前为止得出的结论(仍然有点粗糙):

方法
parse
pack
是泛型的,因此可以将它们移动到元类中。这将使它的使用几乎和我第一次发布的代码片段一样简单


对此解决方案有何评论?仍然在寻找更简单的东西,不确定它是否存在。

这个问题真的很老了:

我有一个更简单的答案,这似乎很奇怪,但它避免了元类,解决了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>
machine:~user$python svs.py
这个答案对我有效有两个原因:

  • 构造函数的参数可以被pickle,并且没有对类型的引用
  • 我使用variableArrayLength定义定义结构内部的所有结构
  • 对于调用者来说,该结构看起来完全相同,就像我刚刚在_字段中定义了数组一样_
  • 我无法修改头文件中定义的底层结构,也无法在不更改任何底层代码的情况下实现我的目标
  • 我不需要修改任何解析/打包逻辑,这只做我正在尝试做的事情,即使用可变长度数组构建类定义
  • 这是一个通用的、可重用的容器,可以像我的其他结构一样发送到工厂

  • 我显然更希望头文件带有指针,但这并不总是可能的。这个答案令人沮丧。其他的则非常适合数据结构本身,或者需要修改调用者

    您可以使用ctypes指针来执行此操作

    C结构

    struct some_struct {
        uint  length;
        uchar data[1];
    };
    
    Python代码

    from ctypes import *
    
    class SomeStruct(Structure):
        _fields_ = [('length', c_uint), ('data', c_ubyte)]
    
    #read data into SomeStruct
    s = SomeStruct()
    ptr_data = pointer(s.data)
    for i in range(s.length):
        print ptr_data[i]
    

    你为什么需要一个结构?你不能直接发送缓冲区吗?我能问一下C
    struct
    是什么样子吗?我假设
    struct{size_t num_points;Point poits[];}
    ,但我可能错了(特别是如果您的C代码使用C99之前的hacks实现灵活的数组成员行为)。没有单一的C结构等价物。我们需要建立一个缓冲区,然后发送它。@ChrisLutz给出的结构定义实际上是可行的,只要您通过malloc(sizeof(struct whatever)+字节数组使用)创建了指向它的指针。正如您在最后指出的,这不适用于将数据接收到。这让我想到了另一种可能的解决方案,虽然该类返回一个大小合适的内部类。使用Beazley方法的好处是我不必计算大小和打包/解包数据。这是我在C代码中可以做到的。我希望能找到一种方法来利用动态na
    from ctypes import *
    
    class SomeStruct(Structure):
        _fields_ = [('length', c_uint), ('data', c_ubyte)]
    
    #read data into SomeStruct
    s = SomeStruct()
    ptr_data = pointer(s.data)
    for i in range(s.length):
        print ptr_data[i]