2016-03-30 4 views
3

Я пытаюсь написать метод __reduce__() для класса cython, который содержит указатели C, но до сих пор нашел очень мало информации о том, как это сделать. Существует множество примеров для правильного написания метода __reduce__() при использовании массивов numpy в качестве данных элемента. Я хотел бы держаться подальше от массивов Numpy, поскольку они, как представляется, всегда хранятся как объекты python и требуют вызова API-интерфейса python и из него. Я исхожу из C-фона, поэтому я очень комфортно работаю с памятью вручную, используя вызовы malloc() и free(), и стараюсь поддерживать взаимодействие python с абсолютным минимумом.Pickle Cython Class с C указателями

Однако я столкнулся с проблемой. Мне нужно использовать что-то эквивалентное copy.deepcopy() для класса, который я создаю, из сценария Python, где он будет в конечном счете использоваться. Я обнаружил, что единственный хороший способ сделать это - реализовать протокол pickle для класса, внедряя метод __reduce__(). Это тривиально с большинством примитивов или объектов python. Однако я нахожусь в абсолютной потере для того, как это сделать для динамически распределенных массивов C. Очевидно, я не могу вернуть сам указатель, поскольку базовая память исчезнет к тому моменту, когда объект будет реконструирован, так что лучший способ сделать это? Я уверен, что это потребует модификации метода __reduce__(), а также одного или обоих методов __init__().

Я прочитал документацию по python по типам растягивающих линий found here, а также почти каждый вопрос о переполнении стека о выборе классов cython, таких как this question.

Сокращенный вариант моего класса выглядит примерно так:

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)) 
+0

Я также читал этот вопрос, но она непосредственно не относится к _pickling_ указателей C массивы. [Cython - преобразование указателей на массивы в объекты Python] (http://stackoverflow.com/questions/5271690/cython-converting-pointers-to-arrays-into-python-objects?rq=1) –

+0

Думаю, вам нужно сериализуйте данные в объект 'bytes' Python. Затем используйте функцию перестроения (например, http://stackoverflow.com/a/12647497/1300519) для возврата в массив int. Мне еще этого не удалось, но я считаю, что это правильный подход. Не публиковать это как ответ, пока у меня не будет рабочего примера. – Snorfalorpagus

ответ

2

Одним из подходов является сериализация данных в массиве в bytes массив Python. Метод __reduce__ сначала вызывает метод get_data, который наводит указатель данных на <char*>, затем на <bytes> (если вы попытаетесь туда прямо, 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

У вас могут возникнуть проблемы, если ваши данные содержат 0 (байты могут заканчиваться раньше)? Но идея выглядит хорошо для меня. – DavidW

+0

@DavidW Я действительно задавался вопросом об этом, но это, похоже, не проблема. 'memcpy' не считает нулевые байты, как некоторые из других строковых функций (я думаю).Я протестировал его, установив середину массива в пример на 0 и, похоже, все в порядке. – Snorfalorpagus

+0

memcpy не так, но я думал, что конструктор байтов может. Если вы его протестировали, то, вероятно, это хорошо! – DavidW

Смежные вопросы