【问题标题】:Pickle Cython Class with C pointers带有 C 指针的 Pickle Cython 类
【发布时间】:2016-03-30 06:30:46
【问题描述】:

我正在尝试为包含 C 指针的 cython 类编写 __reduce__() 方法,但到目前为止,关于执行此操作的最佳方法的信息很少。当使用 numpy 数组作为成员数据时,如何正确编写 __reduce__() 方法的示例很多。我想远离 Numpy 数组,因为它们似乎总是存储为 python 对象,并且需要调用和调用 python API。我来自 C 背景,所以我很乐意通过调用 malloc()free() 手动处理内存,并试图将 python 交互保持在最低限度。

但是我遇到了一个问题。我需要在我正在创建的类上使用与 copy.deepcopy() 等效的东西,来自最终将使用它的 Python 脚本。我发现这样做的唯一好方法是通过实现__reduce__() 方法来实现类的pickle 协议。这对于大多数原语或 python 对象来说都是微不足道的。但是,对于如何为动态分配的 C 数组执行此操作,我完全不知所措。显然,我无法返回指针本身,因为在重建对象时底层内存将消失,那么最好的方法是什么?我确信这将需要修改__reduce__() 方法以及__init__() 方法中的一个或两个。

我已经阅读了有关酸洗扩展类型found here 的python 文档,以及关于选择诸如this question 之类的cython 类的所有其他堆栈溢出问题。

我的课程的精简版如下所示:

cdef class Bin:
    cdef int* job_ids
    cdef int* jobs
    cdef int primitive_data

    def __cinit__(self):
        self.job_ids = <int*>malloc(40 * sizeof(int))
        self.jobs = <int*>malloc(40 * sizeof(int))

    def __init__(self, int val):
        self.primitive_data = val

    def __dealloc__(self):
        free(job_ids)
        free(jobs)

    def __reduce__(self):
        return (self.__class__, (self.primitive_data))

【问题讨论】:

标签: python pointers cython pickle


【解决方案1】:

一种方法是将数组中的数据序列化为 Python bytes 数组。 __reduce__ 方法首先调用get_data 方法,该方法将数据指针转换为&lt;char*&gt;,然后转换为&lt;bytes&gt;(如果您尝试直接去那里,Cython 不知道该怎么做)。 __reduce__ 返回此对象,以及对 rebuild 函数的引用(模块级函数,而不是方法!),可用于使用 set_data 方法重新创建实例。如果您需要传递多个数组,如您的示例所示,您只需要接受更多参数给rebuild 并扩展__reduce__ 返回的元组。

我没有对此进行太多测试,但它似乎有效。如果您传递格式错误的数据,它可能会爆炸。

from cpython.mem cimport PyMem_Malloc, PyMem_Realloc, PyMem_Free
from libc.string cimport memcpy

cdef int length = 40

cdef class MyClass:
    cdef long *data

    def __cinit__(self):
        self.data = <long*>PyMem_Malloc(sizeof(long)*length)
        if not self.data:
            raise MemoryError()

    cdef bytes get_data(self):
        return <bytes>(<char *>self.data)[:sizeof(long)*length]

    cdef void set_data(self, bytes data):
        memcpy(self.data, <char*>data, sizeof(long)*length)

    def set_values(self):
        # assign some dummy data to the array 0..length
        for n in range(0, length):
            self.data[n] = n

    def get(self, i):
        # get the ith value of the data
        return self.data[i]

    def __reduce__(self):
        data = self.get_data()
        return (rebuild, (data,))

    def __dealloc__(self):
        PyMem_Free(self.data)

cpdef object rebuild(bytes data):
    c = MyClass()
    c.set_data(data)
    return c

使用示例(假设 MyClass 在 hello.pyx 中):

import hello
import pickle

c1 = hello.MyClass()
c1.set_values()
print('c1', c1)
print('fifth item', c1.get(5))

d = pickle.dumps(c1)
del(c1)  # delete the original object

c2 = pickle.loads(d)
print('c2', c2)
print('fifth item', c2.get(5))

【讨论】:

  • 如果您的数据包含 0,您可能会遇到问题(字节可能会提前终止)?但我觉得这个主意不错。
  • @DavidW 我确实想知道这一点,但这似乎不是问题。 memcpy 不像其他一些字符串函数(我认为)那样考虑空字节。我已经通过将示例中的数组中间设置为 0 来测试它,它似乎没问题。
  • memcpy 没有,但我认为字节构造函数可能。如果您已经对其进行了测试,那么它可能没问题!
  • 这是一个很好的例子!谢谢@Snorfalorpagus
  • 需要memcpy吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-10-25
  • 2021-04-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-12
  • 2017-04-14
相关资源
最近更新 更多