2014-10-27 9 views
15

Ich habe eine Unterklasse von numpy ndarray folgenden the numpy documentation erstellt. Insbesondere habe ich added a custom attribute durch Änderung des bereitgestellten Codes.Erhalten Sie benutzerdefinierte Attribute beim Beizen Unterklasse von numpy Array

Ich manipuliere Instanzen dieser Klasse in einer parallelen Schleife mit Python multiprocessing. Wie ich es verstehe, verwendet die Art, wie der Bereich im Wesentlichen in mehrere Threads kopiert wird, pickle.

Das Problem, auf das ich jetzt stoße, bezieht sich auf die Art, wie numpy Arrays gebeizt werden. Ich kann keine umfassende Dokumentation darüber finden, aber einige discussions between the dill developers schlagen vor, dass ich mich auf die __reduce__ Methode konzentrieren sollte, die beim Beizen aufgerufen wird.

Kann jemand mehr Licht dazu werfen? Die minimale Arbeitsbeispiel ist wirklich nur das numpy Beispiel Code, den ich oben verlinkt, der Vollständigkeit halber hier kopiert:

import numpy as np 

class RealisticInfoArray(np.ndarray): 

    def __new__(cls, input_array, info=None): 
     # Input array is an already formed ndarray instance 
     # We first cast to be our class type 
     obj = np.asarray(input_array).view(cls) 
     # add the new attribute to the created instance 
     obj.info = info 
     # Finally, we must return the newly created object: 
     return obj 

    def __array_finalize__(self, obj): 
     # see InfoArray.__array_finalize__ for comments 
     if obj is None: return 
     self.info = getattr(obj, 'info', None) 

Jetzt ist hier das Problem:

import pickle 

obj = RealisticInfoArray([1, 2, 3], info='foo') 
print obj.info # 'foo' 

pickle_str = pickle.dumps(obj) 
new_obj = pickle.loads(pickle_str) 
print new_obj.info # raises AttributeError 

Dank.

Antwort

20

np.ndarray verwendet __reduce__, um sich selbst zu gießen. Wir können einen Blick darauf werfen, was es gibt tatsächlich, wenn Sie diese Funktion aufrufen, um eine Vorstellung davon zu bekommen, was los ist:

>>> obj = RealisticInfoArray([1, 2, 3], info='foo') 
>>> obj.__reduce__() 
(<built-in function _reconstruct>, (<class 'pick.RealisticInfoArray'>, (0,), 'b'), (1, (3,), dtype('int64'), False, '\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00')) 

Also, wir wieder ein 3-Tupel erhalten. Die Dokumentation für __reduce__ beschreiben, was jedes Element tut:

Wenn ein Tupel zurückgegeben wird, muss es zwischen zwei und fünf Elementen lang sein. Optionale Elemente können entweder weggelassen werden oder Keine kann als Wert bereitgestellt werden. Der Inhalt dieses Tupels wird wie üblich gebeizt und verwendet, um das Objekt zum Zeitpunkt der Entpixelung zu rekonstruieren. Die Semantik jedes Element ist:

  • Ein aufrufbare Objekt, das die erste Version von das Objekt erstellen aufgerufen wird. Das nächste Element des Tupels liefert Argumente für , die aufrufbar sind, und spätere Elemente liefern zusätzliche Zustandsinformationen , die anschließend verwendet werden, um die gebeizten Daten vollständig zu rekonstruieren.

    Im Unpickling Umgebung dieses Objekt muss entweder eine Klasse, ein aufrufbar registriert als „sichere Konstruktor“ (siehe unten), oder es muss hat ein Attribut __safe_for_unpickling__ mit einem wahren Wert. Andernfalls wird ein UnpicklingError in der unpickling Umgebung ausgelöst. Beachten Sie, dass wie üblich die aufrufbare selbst von Namen gebeizt wird.

  • Ein Tupel von Argumenten für das aufrufbare Objekt.

  • Optional der Status des Objekts, der an die Methode __setstate__() des Objekts übergeben wird, wie im Abschnitt Ein- und Ausbau normaler Klasseninstanzen beschrieben.Wenn das Objekt keine __setstate__() Methode, hat, dann muss der Wert wie oben beschrieben ein Wörterbuch sein und wird des Objekts __dict__ hinzugefügt. So

ist _reconstruct die Funktion aufgerufen, das Objekt neu zu erstellen, sind (<class 'pick.RealisticInfoArray'>, (0,), 'b') die für diese Funktion übergebenen Argumente und (1, (3,), dtype('int64'), False, '\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00')) wird an die Klasse übergeben __setstate__. Dies gibt uns eine Chance; wir könnten __reduce__ überschreiben und unser eigenes Tupel zu __setstate__ zur Verfügung stellen, und dann außer __setstate__ außer Kraft setzen, um unser kundenspezifisches Attribut zu setzen, wenn wir unpickle. Wir müssen nur sicherstellen, dass wir die übergeordnete Klasse braucht, um alle Daten zu erhalten und rufen die Eltern __setstate__ auch:

class RealisticInfoArray(np.ndarray): 
    def __new__(cls, input_array, info=None): 
     obj = np.asarray(input_array).view(cls) 
     obj.info = info 
     return obj 

    def __array_finalize__(self, obj): 
     if obj is None: return 
     self.info = getattr(obj, 'info', None) 

    def __reduce__(self): 
     # Get the parent's __reduce__ tuple 
     pickled_state = super(RealisticInfoArray, self).__reduce__() 
     # Create our own tuple to pass to __setstate__ 
     new_state = pickled_state[2] + (self.info,) 
     # Return a tuple that replaces the parent's __setstate__ tuple with our own 
     return (pickled_state[0], pickled_state[1], new_state) 

    def __setstate__(self, state): 
     self.info = state[-1] # Set the info attribute 
     # Call the parent's __setstate__ with the other tuple elements. 
     super(RealisticInfoArray, self).__setstate__(state[0:-1]) 

Verbrauch:

>>> obj = pick.RealisticInfoArray([1, 2, 3], info='foo') 
>>> pickle_str = pickle.dumps(obj) 
>>> pickle_str 
"cnumpy.core.multiarray\n_reconstruct\np0\n(cpick\nRealisticInfoArray\np1\n(I0\ntp2\nS'b'\np3\ntp4\nRp5\n(I1\n(I3\ntp6\ncnumpy\ndtype\np7\n(S'i8'\np8\nI0\nI1\ntp9\nRp10\n(I3\nS'<'\np11\nNNNI-1\nI-1\nI0\ntp12\nbI00\nS'\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x03\\x00\\x00\\x00\\x00\\x00\\x00\\x00'\np13\nS'foo'\np14\ntp15\nb." 
>>> new_obj = pickle.loads(pickle_str) 
>>> new_obj.info 
'foo' 
+0

groß, daß es behoben hat. Danke auch für das sehr klare Codebeispiel. Ich übertrage tatsächlich über das '__dict__'-Objekt, um dies generischer zu machen. Glücklicherweise scheint np.darray es nicht zu benutzen, also kann ich es für meine eigenen Zwecke verwenden. – Gabriel

6

Ich bin der dill (und pathos) Autor . dill war ein numpy.array Beizen vor numpy könnte es selbst tun. @ Danos Erklärung ist ziemlich genau. Ich persönlich, ich würde einfach dill verwenden und es für Sie erledigen lassen. Mit dill brauchen Sie nicht __reduce__, da dill mehrere Möglichkeiten hat, dass es subclasseded Attribute ergreift ... von denen die für jedes Klassenobjekt speichert. pickle tut dies nicht, b/c es arbeitet normalerweise mit Klassen durch Namensbezug und speichert nicht das Klassenobjekt selbst ... so müssen Sie mit __reduce__ arbeiten, um pickle arbeiten für Sie zu machen. In den meisten Fällen nicht mit dill.

>>> import numpy as np 
>>> 
>>> class RealisticInfoArray(np.ndarray): 
...  def __new__(cls, input_array, info=None): 
...   # Input array is an already formed ndarray instance 
...   # We first cast to be our class type 
...   obj = np.asarray(input_array).view(cls) 
...   # add the new attribute to the created instance 
...   obj.info = info 
...   # Finally, we must return the newly created object: 
...   return obj 
...  def __array_finalize__(self, obj): 
...   # see InfoArray.__array_finalize__ for comments 
...   if obj is None: return 
...   self.info = getattr(obj, 'info', None) 
... 
>>> import dill as pickle 
>>> obj = RealisticInfoArray([1, 2, 3], info='foo') 
>>> print obj.info # 'foo' 
foo 
>>> 
>>> pickle_str = pickle.dumps(obj) 
>>> new_obj = pickle.loads(pickle_str) 
>>> print new_obj.info 
foo 

dill kann sich in pickle erstrecken (im Wesentlichen durch copy_reg alles, was es weiß), so können Sie dann alle dill Typen in alles, was pickle verwendet verwenden. Nun, wenn Sie multiprocessing verwenden werden, sind Sie ein bisschen geschraubt, da es cPickle verwendet. Es gibt jedoch die pathos Verzweigung von multiprocessing (genannt pathos.multiprocessing), die im Grunde die einzige Änderung ist, dass sie dill anstelle von cPickle verwendet ... und somit in einer Pool.map serialisieren kann. Ich denke (derzeit), wenn Sie mit Ihrer Unterklasse von numpy.array in multiprocessing (oder pathos.multiprocessing) arbeiten möchten, müssen Sie etwas tun, wie @dano schlägt - aber nicht sicher, da ich nicht an einen guten Fall dachte an der Spitze meines Kopfes, um deine Unterklasse zu testen.

Wenn Sie interessiert sind, erhalten pathos hier: https://github.com/uqfoundation

+0

Brilliante Antwort, vielen Dank. Der einzige Grund, warum ich das nicht akzeptierte, ist, dass der @ dano-Vorschlag ohne Paketwechsel funktioniert. FWIW, ich stimme dem zu, dass Dill immer vorzuziehen ist und in Zukunft stark nach Pathos suchen wird. – Gabriel

+0

Gabriel, in diesem Fall hätte ich auch die Antwort von @ dano über meine genommen, aber ich dachte, je mehr Informationen für Sie, desto besser. :) –

Verwandte Themen