我有以下 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部分重叠。)
但是,指针不保存/具有有关存储数据占用多少内存的信息(有些人可能会认为这是指向类型的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/