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, seUnpicklingError
unUnpicklingError
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''