2016-03-30 19 views
3

Ich versuche, eine __reduce__()-Methode für eine Cython-Klasse zu schreiben, die C-Zeiger enthält, aber bis jetzt sehr wenig Informationen über den besten Weg gefunden haben, dies zu tun. Es gibt Unmengen von Beispielen dafür, wie man eine __reduce__()-Methode richtig schreibt, wenn man numpy-Arrays als Mitgliedsdaten verwendet. Ich möchte mich von Numpy-Arrays fernhalten, da sie immer als Python-Objekte gespeichert sind und Aufrufe von und an die Python-API erfordern. Ich komme von einem C-Hintergrund, also bin ich sehr komfortabel mit dem Speicher arbeiten manuell mit Calls zu malloc() und free() und versuche, Python Interaktion auf ein absolutes Minimum zu halten.Pickle Cython-Klasse mit C-Zeigern

Allerdings habe ich ein Problem festgestellt. Ich muss etwas Ähnliches wie copy.deepcopy() für die Klasse, die ich erstelle, aus dem Python-Skript verwenden, wo es letztendlich verwendet wird. Ich habe gefunden, dass der einzige gute Weg dies zu tun ist, das Beizprotokoll für die Klasse zu implementieren, indem man eine __reduce__() Methode einführt. Dies ist bei den meisten Primitiven oder Python-Objekten trivial. Ich bin jedoch absolut im Verlust, wie ich dies für dynamisch zugewiesene C-Arrays tun kann. Offensichtlich kann ich den Zeiger selbst nicht zurückgeben, da der zugrunde liegende Speicher zum Zeitpunkt der Rekonstruktion des Objekts verschwunden ist. Was ist der beste Weg, dies zu tun? Ich bin mir sicher, dass dies sowohl eine Modifikation der __reduce__()-Methode als auch eine oder beide der __init__()-Methoden erfordert.

Ich habe die Python-Dokumentation über Beizen Erweiterungstypen found here sowie fast jede andere Frage des Stacküberlaufs über die Auswahl von Cython-Klassen wie this question gelesen.

Eine komprimierte Version meiner Klasse sieht ungefähr so ​​aus:

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

ich auch diese Frage gelesen habe, aber es gilt nicht direkt C Zeiger auf Arrays _pickling_. [Cython - Zeiger auf Arrays in Python-Objekte konvertieren] (http://stackoverflow.com/questions/5271690/cython-converting-pointers-to-arrays-into-python-objects?rq=1) –

+0

Ich denke, Sie müssen Serialisieren Sie die Daten in ein Python-bytes-Objekt. Verwenden Sie dann eine Neuerstellungsfunktion (z. B. http://stackoverflow.com/a/12647497/1300519), um in ein int-Array zurückzuspringen. Ich habe es noch nicht geschafft, das selbst zu schaffen, aber ich glaube, das ist der richtige Ansatz. Ich schreibe das nicht als Antwort, bis ich ein funktionierendes Beispiel habe. – Snorfalorpagus

Antwort

2

Ein Ansatz ist es, die Daten in das Array in eine Python bytes Array serialise. Die Methode __reduce__ ruft zuerst die Methode get_data auf, die den Datenzeiger auf <char*> und dann auf <bytes> überträgt (wenn Sie versuchen, direkt dorthin zu gehen, weiß Cython nicht, wie es geht). __reduce__ gibt dieses Objekt zurück, zusammen mit einem Verweis auf die rebuild-Funktion (eine Modulebenenfunktion, keine Methode!), Die verwendet werden kann, um die Instanz unter Verwendung der set_data-Methode neu zu erstellen. Wenn Sie wie in Ihrem Beispiel mehr als ein Array übergeben müssen, müssen Sie nur weitere Argumente an rebuild akzeptieren und das Tupel, das von __reduce__ zurückgegeben wird, erweitern.

Ich habe nicht viel getestet, aber es scheint zu funktionieren. Es würde wahrscheinlich explodieren, wenn Sie fehlerhafte Daten übergeben würden.

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 

Beispiel der Verwendung (MyClass Annahme, daß in 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

Sie könnten Probleme haben, wenn Ihre Daten 0s enthalten (Bytes könnten null vorzeitig beenden)? Aber die Idee sieht gut aus. – DavidW

+0

@DavidW Ich habe mich gefragt, aber es scheint kein Problem zu sein. 'memcpy' berücksichtigt keine Null-Bytes wie einige der anderen String-Funktionen (glaube ich).Ich habe es getestet, indem ich die Mitte des Arrays in Beispiel auf 0s setze und es scheint OK. – Snorfalorpagus

+0

memcpy tut nicht, aber ich dachte, der Byte-Konstruktor könnte. Wenn Sie es getestet haben, ist es wahrscheinlich in Ordnung! – DavidW