[Python] pickle di una classe derivata

Marco Giusti marco.giusti a gmail.com
Mer 5 Dic 2012 19:13:28 CET


On Wed, Dec 05, 2012 at 06:13:57PM +0100, Simone Ziraldo wrote:
> On 05/dic/2012, at 10.05, Marco Giusti wrote:
> 
> > On Wed, Dec 05, 2012 at 12:04:54AM +0100, Simone Ziraldo wrote:
> >> Ciao a tutti,
> >> 
> >> il problema che ho è questo: quando faccio il pickle di una classe derivata di numpy.ndarray non mi salva gli attributi che ho definito io.
> >> Un codice semplice che genera questo problema è questo:
> >> 
> >> import pickle
> >> import numpy
> >> 
> >> class prova( numpy.ndarray ):
> >>    def __new__(cls,size):
> >>        return numpy.ndarray.__new__( cls, size )
> >>    def __init__(self,size ):
> >>        self.__size__ = size
> >> 
> >> s = prova(2)
> >> 
> >> f = open("prova.out", 'w')
> >> pickle.dump(s,f)
> >> f.close()
> >> 
> >> f = open("prova.out", 'r')
> >> obj = pickle.load(f)
> >> f.close
> >> 
> >> print obj.__size__
> >> 
> >> e mi viene restituito questo errore:
> >> AttributeError: 'prova' object has no attribute '__size__'
> > 
> > 	>>> class prova2(numpy.ndarray):
> > 	...  def __init__(self, size):
> > 	...   numpy.ndarray.__init__(self, size)
> > 	...   self.__size__ = size
> > 	...  def __reduce__(self):
> > 	...   t = numpy.ndarray.__reduce__(self)
> > 	...   klass, args = t[:2]
> > 	...   func, args = t[:2]
> > 	...   return (reconstruct, (func, args, self.__size__)) + t[2:]
> > 	...
> > 	>>> def reconstruct(f, args, size):
> > 	...  o = f(*args)
> > 	...  o.__size__ = size
> > 	...  return o
> > 	...
> > 	>>> p3 = pickle.loads(pickle.dumps(p2))
> > 	>>> p3
> > 	prova2([  6.90286283e-310,   6.90286283e-310,   1.58101007e-322])
> > 	>>> p3.__dict__
> > 	{'__size__': 3}
> > 	>>> 
> > 
> > la documentazione su pickle spiega tutto
> 
> Grazie!
> 
> Mi sono guardato la documentazione e mi sono rimasti dei dubbi.
> Ad esempio non mi è ben chiaro quando nel dump decide di usare il
> metodo __reduce__ invece di __getstate__. La guida dice che usa
> __reduce__ "When the Pickler encounters an object of a type it knows
> nothing about", però ad esempio se faccio il dump di una classe nuova
> non usa __reduce__ ma __getstate__.
> Ad esempio:
> class trial():
>     def __reduce__(self):
>         print "Hello from reduce"
>     def __getstate__(self):
>         print "Hello from getstate"
> pickle.loads(pickle.dumps( trial()))
> 
> stampa: Hello from getstate
> 
> Quindi, trial è un type che "conosce", mentre una classe derivata da
> numpy.ndarray (nell'esempio iniziale) è un type che non conosce...

la semantica di pickle è cambiata con l'introduzione dei new-style
object. quando definisci una classe nel seguente modo:

	class trial():
		...

le parentesi sono ridondanti e stai definendo una classe ti dipo
"vecchio". per definire una classe di tipo "nuovo" devi ereditare da una
classe di tipo nuovo, quasi tutti i tipi base di python (tutti?) sono
di tipo nuovo, o da `object` nel caso più generale.

	class trial(object):
		...

in questo caso la nuova semantica di pickle entra in gioco[1] e
`__reduce__` viene chiamato. Non conosco tutti i particolari io e
piuttosto che dirti stupidaggini ti rimando comunque alla documentazione
ma ti lascio con un ultimo esempio:


	>>> class C(object):
	...  def __getstate__(self):
	...   print 'C.__getstate__'
	...   return 'C'
	...  def __setstate__(self, state):
	...   print 'C.__setstate__, val:', state
	...  def __reduce__(self):
	...   print 'C.__reduce__'
	...   return object.__reduce__(self)
	... 
	>>> c=C()
	>>> pickle.dumps(c)
	C.__reduce__
	C.__getstate__
	"ccopy_reg\n_reconstructor\np0\n(c__main__\nC\np1\nc__builtin__\nobject\np2\nNtp3\nRp4\nS'C'\np5\nb."
	>>> c2 = pickle.loads(pickle.dumps(c))
	C.__reduce__
	C.__getstate__
	C.__setstate__, val: C

come vedi è l'implementazione standard di `__reduce__` che chiama
`__getstate__`. inoltre un extension type è un tipo scritto in c, ed
immagino che cython, swig, ecc. rientrino tutti in questa categoria, e
quindi è verosimile che pickle usi `__reduce__` di default per i tipi di
numpy.

spero che tutto ti sia un po' più chiaro.

ps. tutto questo vale per python2, per python3 le cose credo che cambino
ancora, per esempio non credo che esistano più le classi "vecchio"
stile.

ciao
m.

[1] http://www.python.org/dev/peps/pep-0307/


Maggiori informazioni sulla lista Python