python - 使用 ct.Pointer 对 ctypes.Structure 进行 pickle

标签 python pickle ctypes

我有以下 ctypes 结构类。

import ctypes as ct

class my_array(ct.Structure):
    _fields_ = [("_data", ct.POINTER(ct.c_uint32)),
                ("_size", ct.c_size_t)]

    def __init__(self, data):
        self._data = (ct.c_uint32 * len(data))()
        for i in range(0, len(data)):
            self._data[i] = data[i]
            self._size = len(data)

    def __reduce__(self):
        data = [None]*self._size
        for j in xrange(0, self._size):
            data[j] = self._data[j]
        return (my_array, (data,))


class my_struct(ct.Structure):
    _fields_ = [("numbs", my_array)]

但是,当我 pickle 第二个对象时

import cPickle
a = my_array([1, 2, 10])
b = my_struct(a)

with open('myfile', 'wb') as f:
    cPickle.dump(a, f)  # This succeeds
    cPickle.dump(b, f)  # This fails

我得到了异常(exception)

Traceback (most recent call last):
  File "tmp.py", line 30, in <module>
    cPickle.dump(a, f)  # This fails
ValueError: ctypes objects containing pointers cannot be pickled

我不明白为什么会发生这种情况,因为我在 my_array 中实现了 __reduce__ ?实现 __getstate__ 也不起作用。

我知道我可以在 my_struct 中再次重载 __reduce__ 但这对我来说似乎过于复杂,因为从那时起我每次都必须继续重载 __reduce__在结构中包含 my_array

最佳答案

死亡帖子。

我在调查另一个问题时遇到了同样的情况(使用 Python 3)。

列表:

指针是一个(起始)内存地址,其中可以(也可以不)存储数据(多个字节)。通常,当在结构成员中使用时,指针用于存储元素数组(element 表示指向类型),就像您的示例中一样,因为:

  1. 避免数组限制

  2. 灵活性:可以对多个元素使用相同的结构(实例)(与#1部分重叠。)

但是,指针不保存/具有有关存储数据占用多少内存的信息(有些人可能会认为这是指向类型的sizeof,但这更多就像一个提示)。
因此,为了能够从指针地址获取正确的字节数(以便pickle(或对其进行任何操作)),还必须检索数据大小来自外部(当前指针明智)源(在我们的例子中来自另一个结构成员)。
如果大小不可用,则后备将是(如上所述)指向类型的sizeof,但在某些情况下这可能不正确。当结构嵌套时(这种情况很常见),事情会变得更加复杂。此外,数据大小可能以字节或元素表示

因此,没有通用方法可以知道一个地址存储了多少字节,这正是为什么 (CTypes) 指针不能默认情况下pickle(否则可能会出现访问“禁止”内存的情况,这是U未定义的B行为并且可能会触发SegFault(访问冲突))。
因此,包含指针的结构(联合或任何其他容器类型)应该定义自己的pickl策略(取决于它们存储/解释数据的方式)。

我创建了一个小例子。

code00.py:

#!/usr/bin/env python

import ctypes as ct
import pickle
import sys


def __reduce__(self):
    #print("__reduce__")
    state = []
    ptr_size = None
    for name, typ in self._fields_:
        val = getattr(self, name)
        if issubclass(typ, ct._Pointer):
            state.append(val[:ptr_size])
            ptr_size = None
        else:
            state.append(val)
            ptr_size = val
    #print("pickle state:", state)
    return (self.__class__, (), state)


def __setstate__(self, state):
    #print("__setstate__")
    for idx, (name, typ) in enumerate(self._fields_):
        if issubclass(typ, ct._Pointer):
            val = state[idx]
            setattr(self, name, (typ._type_ * len(val))(*val))
        else:
            setattr(self, name, state[idx])


def to_string(self, indent=0, head=True, tail=True, indent_text="  "):
    l = [""] if head else []
    l.append("{:s}{:s}".format(indent_text * indent, str(self)))
    i1 = indent_text * (indent + 1)
    ptr_size = None
    for name, typ in self._fields_:
        val = getattr(self, name)
        inner = getattr(val, to_string.__name__, None)
        if callable(inner):
            l.append("{:s}{:s}: {:}".format(i1, name, inner(indent=indent + 1, head=False, tail=False, indent_text=indent_text)))
        elif issubclass(typ, ct._Pointer):
            l.append("{:s}{:s} ({:}): ({:s})".format(i1, name, val, ", ".join(str(val[e]) for e in range(ptr_size))))
            ptr_size = None
        else:
            l.append("{:s}{:s}: {:}".format(i1, name, val))
            ptr_size = val
    if tail:
        l.append("")
    return "\n".join(l)


FloatPtr = ct.POINTER(ct.c_float)
StrPtr = ct.POINTER(ct.c_char_p)


class Struct0(ct.Structure):
    _fields_ = (
        ("float_size", ct.c_uint),
        ("float_data", FloatPtr),
        ("str_size", ct.c_uint),
        ("str_data", StrPtr),
    )

    '''
    def __getstate__(self):
        print("__getstate__")
        return ""
    '''

Struct0.__reduce__ = __reduce__
Struct0.__setstate__ = __setstate__
Struct0.to_string = to_string


class Struct1(ct.Structure):
    _fields_ = (
        ("struct0", Struct0),
        ("i", ct.c_int),
    )

Struct1.__reduce__ = __reduce__
Struct1.__setstate__ = __setstate__
Struct1.to_string = to_string


def main(*argv):
    floats = (
        3.141593,
        2.718282,
        1.618,
        -1,
    )
    strs = (
        "dummy",
        "",
        "stupid",
        "text",
        "",
    )

    s00 = Struct0()
    s00.float_size = len(floats)
    s00.float_data = (ct.c_float * s00.float_size)(*floats)
    s00.str_size = len(strs)
    s00.str_data = (ct.c_char_p * s00.str_size)(*(ct.c_char_p(e.encode()) for e in strs))

    print("\nORIGINAL:", s00.to_string())
    s00p = pickle.dumps(s00)
    print("PICKLED:\n", s00p)
    s01 = pickle.loads(s00p)
    print("\nUNPICKLED:", s01.to_string())

    #print(dir(s00) == dir(s01), s00.__dict__ == s01.__dict__, s00 == s01, Struct0() == Struct0())

    s10 = Struct1()
    s10.struct0 = s00
    s10.i = -69
    print("\nORIGINAL:", s10.to_string())
    s10p = pickle.dumps(s10)
    print("PICKLED:\n", s10p)
    s11 = pickle.loads(s10p)
    print("\nUNPICKLED:", s11.to_string())


if __name__ == "__main__":
    print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
                                                   64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    rc = main(*sys.argv[1:])
    print("\nDone.")
    sys.exit(rc)

注释:

  • 我创建了所需的方法(__reduce____setstate__ - 因为如果不unpicklpickl就没有意义 em>ing),还有一个(to_string - 很好地显示结构)。他们使用相同的字段“浏览”机制

  • 代码可能看起来有点太复杂,但我想避免硬编码成员名称或类型,这样如果这些更改,方法仍然有效

    • 但是我对结构的状态进行了硬编码(尽管它本身不是硬编码):代码严重依赖于持有大小的成员(在元素中)这一事实) 就在指针一之前,因此如果结构的结构发生变化(我认为这种情况不太可能发生),它将不再起作用
  • 由于我在两个结构中使用了相同的方法实现,因此我将它们定义为函数,并在每个结构定义之后将它们“转换”为方法

    • 相反,它们只处理像这 2 这样的结构类型,这意味着目前不支持某些功能(例如:数组、指向结构的指针或其他我没有想到的功能) 。添加它们应该不会很难,但我不想让代码变得更加复杂
  • 请记住,来回转换数据的成本很高,因此如果经常这样做,通过使用 CType(这是其主要优势)所获得的任何速度改进都将受到严重影响

输出:

[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q049694832]> "e:\Work\Dev\VEnvs\py_pc064_03.09_test0\Scripts\python.exe" ./code00.py
Python 3.9.9 (tags/v3.9.9:ccb0e6a, Nov 15 2021, 18:08:50) [MSC v.1929 64 bit (AMD64)] 064bit on win32


ORIGINAL:
<__main__.Struct0 object at 0x00000191331E40C0>
  float_size: 4
  float_data (<__main__.LP_c_float object at 0x00000191349C5140>): (3.1415929794311523, 2.7182819843292236, 1.6180000305175781, -1.0)
  str_size: 5
  str_data (<__main__.LP_c_char_p object at 0x00000191349C5140>): (b'dummy', b'', b'stupid', b'text', b'')

PICKLED:
 b'\x80\x04\x95m\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x07Struct0\x94\x93\x94)R\x94]\x94(K\x04]\x94(G@\t!\xfb\x80\x00\x00\x00G@\x05\xbf\n\xa0\x00\x00\x00G?\xf9\xe3T\x00\x00\x00\x00G\xbf\xf0\x00\x00\x00\x00\x00\x00eK\x05]\x94(C\x05dummy\x94C\x00\x94C\x06stupid\x94C\x04text\x94h\x08eeb.'

UNPICKLED:
<__main__.Struct0 object at 0x00000191349C5140>
  float_size: 4
  float_data (<__main__.LP_c_float object at 0x00000191349C5240>): (3.1415929794311523, 2.7182819843292236, 1.6180000305175781, -1.0)
  str_size: 5
  str_data (<__main__.LP_c_char_p object at 0x00000191349C5240>): (b'dummy', b'', b'stupid', b'text', b'')


ORIGINAL:
<__main__.Struct1 object at 0x00000191349C5240>
  struct0:   <__main__.Struct0 object at 0x00000191349C53C0>
    float_size: 4
    float_data (<__main__.LP_c_float object at 0x00000191349C5440>): (3.1415929794311523, 2.7182819843292236, 1.6180000305175781, -1.0)
    str_size: 5
    str_data (<__main__.LP_c_char_p object at 0x00000191349C5440>): (b'dummy', b'', b'stupid', b'text', b'')
  i: -69

PICKLED:
 b'\x80\x04\x95\x88\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x07Struct1\x94\x93\x94)R\x94]\x94(h\x00\x8c\x07Struct0\x94\x93\x94)R\x94]\x94(K\x04]\x94(G@\t!\xfb\x80\x00\x00\x00G@\x05\xbf\n\xa0\x00\x00\x00G?\xf9\xe3T\x00\x00\x00\x00G\xbf\xf0\x00\x00\x00\x00\x00\x00eK\x05]\x94(C\x05dummy\x94C\x00\x94C\x06stupid\x94C\x04text\x94h\x0ceebJ\xbb\xff\xff\xffeb.'

UNPICKLED:
<__main__.Struct1 object at 0x00000191349C5440>
  struct0:   <__main__.Struct0 object at 0x00000191349C53C0>
    float_size: 4
    float_data (<__main__.LP_c_float object at 0x00000191349C54C0>): (3.1415929794311523, 2.7182819843292236, 1.6180000305175781, -1.0)
    str_size: 5
    str_data (<__main__.LP_c_char_p object at 0x00000191349C54C0>): (b'dummy', b'', b'stupid', b'text', b'')
  i: -69


Done.

关于python - 使用 ct.Pointer 对 ctypes.Structure 进行 pickle ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49694832/

相关文章:

python - 将反引号 (`) + 分数的字符串转换为 float

python - Azure VM 似乎会终止长时间运行的 MySql 查询

python - 在搁置中保存 lxml 元素时出现类型错误

python - 如何解决pickle编码问题?

python - 尝试多处理时无法 pickle 本地对象

python - "WindowsError: exception: access violation..."- ctypes 问题

python - 使用 ctypes 使用 Ctrl-C 中断 Python C 扩展

python - 保留(pickle)自定义 sklearn 管道的推荐方法是什么?

Python:如果存在空值,如何将 Pyspark 列转换为日期类型

python - 将一组 NumPy 数组传递给 C 函数以进行输入和输出