python arrays numpy pickle python-multiprocessing

python - Preserve los atributos personalizados al seleccionar una subclase de una matriz numpy



arrays pickle (3)

Aquí hay una ligera mejora en la respuesta de @ dano y en el comentario de @ Gabriel. Aprovechar el atributo __dict__ para la serialización funciona para mí incluso con subclases.

def __reduce__(self): # Get the parent''s __reduce__ tuple pickled_state = super(RealisticInfoArray, self).__reduce__() # Create our own tuple to pass to __setstate__, but append the __dict__ rather than individual members. new_state = pickled_state[2] + (self.__dict__,) # 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.__dict__.update(state[-1]) # Update the internal dict from state # Call the parent''s __setstate__ with the other tuple elements. super(RealisticInfoArray, self).__setstate__(state[0:-1])

Aquí hay un ejemplo completo: https://onlinegdb.com/SJ88d5DLB

He creado una subclase de ndarray numpy siguiendo la documentación numpy . En particular, he agregado un atributo personalizado modificando el código proporcionado.

Estoy manipulando instancias de esta clase dentro de un bucle paralelo, usando multiprocessing Python. Según tengo entendido, la forma en que el alcance se ''copia'' esencialmente a múltiples hilos es usando pickle .

El problema al que me enfrento ahora se relaciona con la forma en que las matrices numpy se conservan en vinagre. No puedo encontrar ninguna documentación completa sobre esto, pero algunas discusiones entre los desarrolladores de eneldo sugieren que debería __reduce__ en el método __reduce__ , que se llama al decapado.

¿Alguien puede arrojar más luz sobre esto? El ejemplo de trabajo mínimo es realmente el código de ejemplo numpy al que he vinculado anteriormente, copiado aquí para completar:

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)

Ahora aquí está el problema:

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

Gracias.


Soy el autor dill (y pathos ). dill estaba encurtiendo un numpy.array antes de que numpy pudiera hacerlo por sí mismo. La explicación de @ dano es bastante precisa. Yo personalmente, solo usaría dill y dejaría que haga el trabajo por ti. Con el dill , no necesita __reduce__ , ya que el dill tiene varias formas de agarrar atributos subclasificados ... uno de los cuales es almacenar el __dict__ para cualquier objeto de clase. pickle no hace esto, b / c generalmente funciona con clases por referencia de nombre y no almacena el objeto de la clase en sí ... por lo que debe trabajar con __reduce__ para que pickle funcione para usted. No es necesario, en la mayoría de los casos, con 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 puede extenderse a pickle (esencialmente copy_reg todo lo que sabe), por lo que puede usar todos los tipos de dill en cualquier cosa que use pickle . Ahora, si vas a usar multiprocessing , estás un poco jodido, ya que usa cPickle . Sin embargo, existe la bifurcación pathos del multiprocessing (llamada pathos.multiprocessing ), que básicamente el único cambio es que usa dill lugar de cPickle ... y, por lo tanto, puede serializar muchísimo más en un Pool.map . Creo que (actualmente) si desea trabajar con su subclase de un numpy.array en multiprocessing (o pathos.multiprocessing ), es posible que deba hacer algo como sugiere @dano, pero no estoy seguro, ya que no pensé en un buen caso fuera de mi cabeza para probar tu subclase.

Si está interesado, obtenga pathos aquí: https://github.com/uqfoundation


np.ndarray usa __reduce__ para __reduce__ . Podemos echar un vistazo a lo que realmente devuelve cuando llama a esa función para tener una idea de lo que está sucediendo:

>>> 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''))

Entonces, recuperamos una tupla de 3. Los documentos para __reduce__ describen lo que hace cada elemento:

Cuando se devuelve una tupla, debe tener entre dos y cinco elementos de longitud. Se pueden omitir elementos opcionales o se puede proporcionar Ninguno como su valor. El contenido de esta tupla se conserva en vinagre de forma normal y se utiliza para reconstruir el objeto en el momento de la desconexión. La semántica de cada elemento son:

  • Un objeto invocable que se llamará para crear la versión inicial del objeto. El siguiente elemento de la tupla proporcionará argumentos para este invocable, y los elementos posteriores proporcionarán información de estado adicional que posteriormente se utilizará para reconstruir completamente los datos encurtidos.

    En el entorno de desempaquetado, este objeto debe ser una clase, un invocable registrado como un "constructor seguro" (ver más abajo) o debe tener un atributo __safe_for_unpickling__ con un valor verdadero. De lo contrario, se UnpicklingError un UnpicklingError en el entorno de no parpadeo. Tenga en cuenta que, como de costumbre, el invocable en sí se encurtirá por nombre.

  • Una tupla de argumentos para el objeto invocable.

  • Opcionalmente, el estado del objeto, que se pasará al método __setstate__() del objeto __setstate__() como se describe en la sección Encurtido y desempaquetado de instancias de clase normales. Si el objeto no tiene el método __setstate__() , entonces, como se __setstate__() anteriormente, el valor debe ser un diccionario y se agregará al __dict__ del objeto.

Entonces, _reconstruct es la función llamada para reconstruir el objeto, (<class ''pick.RealisticInfoArray''>, (0,), ''b'') son los argumentos pasados ​​a esa función, y (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'')) se pasa a la clase'' __setstate__ . Esto nos da una oportunidad; podríamos anular __reduce__ y proporcionar nuestra propia tupla a __setstate__ , y luego anular adicionalmente __setstate__ , para establecer nuestro atributo personalizado cuando deseleccionemos. Solo necesitamos asegurarnos de preservar todos los datos que necesita la clase principal, y también llamar al __setstate__ los padres:

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])

Uso:

>>> 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''