Python: Decapado un diccionario con algunos artículos unpicklable
-
28-09-2019 - |
Pregunta
Tengo una gui_project
objeto que tiene una .namespace
atributo, que es un espacio de nombres dict. (Es decir, un dict de cadenas a objetos.)
(Esto se utiliza en un IDE-como programa para permitir al usuario definir su propio objeto en una cáscara de Python.)
Quiero decapado de este gui_project
, junto con el espacio de nombres. El problema es que algunos objetos del espacio de nombres (valores es decir, de la dict .namespace
) no son objetos estibables. Por ejemplo, algunos de ellos se refieren a los widgets wxPython.
Me gustaría filtrar los objetos unpicklable, es decir, les excluyen de la versión en escabeche.
¿Cómo puedo hacer esto?
(Una cosa que he intentado es ir uno por uno en los valores y tratar de decapado de ellos, pero algunos recursividad infinita que pasó, y tengo que estar a salvo de eso.)
(hago implementar un método GuiProject.__getstate__
en este momento, para deshacerse de otras cosas además de unpicklable namespace
.)
Solución 3
Terminé codificación de mi propia solución a esto, utilizando el enfoque de Shane Hathaway.
Aquí está el código . (Busque CutePickler
y CuteUnpickler
.) Aquí son las pruebas . Es parte de GarlicSim , por lo que se puede utilizar por instalación garlicsim
y haciendo from garlicsim.general_misc import pickle_tools
.
Si desea usarlo en código Python 3, utilice el Python 3 tenedor de garlicsim
.
Otros consejos
Yo usaría el apoyo documentado de la pickler de referencias a objetos persistentes. referencias a objetos persistentes son objetos que se hace referencia por la salmuera, pero no almacenado en la salmuera.
http://docs.python.org/ biblioteca / pickle.html # decapado-y-deserialiación-externa-objetos
ZODB ha utilizado esta API desde hace años, por lo que es muy estable. Cuando deserialiación, puede reemplazar las referencias a objetos con lo que quiera. En su caso, que se quiere reemplazar las referencias a objetos con marcadores que indican que los objetos no podían ser encurtidos.
Se podría empezar con algo como esto (no probado):
import cPickle
def persistent_id(obj):
if isinstance(obj, wxObject):
return "filtered:wxObject"
else:
return None
class FilteredObject:
def __init__(self, about):
self.about = about
def __repr__(self):
return 'FilteredObject(%s)' % repr(self.about)
def persistent_load(obj_id):
if obj_id.startswith('filtered:'):
return FilteredObject(obj_id[9:])
else:
raise cPickle.UnpicklingError('Invalid persistent id')
def dump_filtered(obj, file):
p = cPickle.Pickler(file)
p.persistent_id = persistent_id
p.dump(obj)
def load_filtered(file)
u = cPickle.Unpickler(file)
u.persistent_load = persistent_load
return u.load()
A continuación, simplemente llame a dump_filtered () y load_filtered () en lugar de pickle.dump () y pickle.load (). objetos wxPython serán decapados como identificadores persistentes, para ser reemplazado con FilteredObjects en el momento deserialiación.
Se podría hacer que la solución más genérica mediante la filtración de objetos que no son de la __getstate__
ningún método tipos integrados y tienen.
Actualizar (15 nov 2010): Esta es una manera de lograr lo mismo con clases de envoltura. El uso de envoltorio clases en lugar de subclases, es posible permanecer dentro de la API documentada.
from cPickle import Pickler, Unpickler, UnpicklingError
class FilteredObject:
def __init__(self, about):
self.about = about
def __repr__(self):
return 'FilteredObject(%s)' % repr(self.about)
class MyPickler(object):
def __init__(self, file, protocol=0):
pickler = Pickler(file, protocol)
pickler.persistent_id = self.persistent_id
self.dump = pickler.dump
self.clear_memo = pickler.clear_memo
def persistent_id(self, obj):
if not hasattr(obj, '__getstate__') and not isinstance(obj,
(basestring, int, long, float, tuple, list, set, dict)):
return "filtered:%s" % type(obj)
else:
return None
class MyUnpickler(object):
def __init__(self, file):
unpickler = Unpickler(file)
unpickler.persistent_load = self.persistent_load
self.load = unpickler.load
self.noload = unpickler.noload
def persistent_load(self, obj_id):
if obj_id.startswith('filtered:'):
return FilteredObject(obj_id[9:])
else:
raise UnpicklingError('Invalid persistent id')
if __name__ == '__main__':
from cStringIO import StringIO
class UnpickleableThing(object):
pass
f = StringIO()
p = MyPickler(f)
p.dump({'a': 1, 'b': UnpickleableThing()})
f.seek(0)
u = MyUnpickler(f)
obj = u.load()
print obj
assert obj['a'] == 1
assert isinstance(obj['b'], FilteredObject)
assert obj['b'].about
Así es como me gustaría hacer esto (he hecho algo similar antes y funcionó):
- escribir una función que determina si o no un objeto es pickleable
- Haga una lista de todas las variables pickleable, basado en la función anterior
- Crear un nuevo diccionario (llamado D) que almacena todas las variables no pickleable
- Para cada variable en D (esto sólo funciona si tiene las variables muy similares en d) hacer una lista de cadenas, donde cada cadena es el código Python legal, de tal manera que cuando todas estas cadenas se ejecutan en orden, se obtiene la variable deseada
Ahora, cuando unpickle, a recuperar todas las variables que fueron originalmente pickleable. Para todas las variables que no eran pickleable, ahora tiene una lista de cadenas (código Python legal) que cuando se ejecutan en orden, le da la variable deseada.
Espero que esto ayude
Un enfoque sería heredar de pickle.Pickler
, y reemplazar el método save_dict()
. Copiarlo de la clase base, que dice así:
def save_dict(self, obj):
write = self.write
if self.bin:
write(EMPTY_DICT)
else: # proto 0 -- can't use EMPTY_DICT
write(MARK + DICT)
self.memoize(obj)
self._batch_setitems(obj.iteritems())
Sin embargo, en los _batch_setitems, pasar un iterador que filtra todos los objetos que no quiere que sean objeto de dumping, por ejemplo
def save_dict(self, obj):
write = self.write
if self.bin:
write(EMPTY_DICT)
else: # proto 0 -- can't use EMPTY_DICT
write(MARK + DICT)
self.memoize(obj)
self._batch_setitems(item for item in obj.iteritems()
if not isinstance(item[1], bad_type))
Como save_dict no es una API oficial, es necesario comprobar para cada nueva versión de Python si esta anulación sigue siendo correcta.
La parte filtrante es de hecho difícil. El uso de trucos simples, usted puede conseguir fácilmente la salmuera al trabajo. Sin embargo, es posible que terminan filtrando demasiado y perder la información que se puede mantener cuando el filtro se ve un poco más profundo. Pero la gran posibilidad de las cosas que pueden terminar en el .namespace
hace que la construcción de un filtro de buena difícil.
Sin embargo, se podría aprovechar piezas que ya forman parte de Python, como deepcopy
en el módulo copy
.
Me hizo una copia del módulo de copy
, e hizo lo siguiente:
- crear un nuevo tipo llamado
LostObject
para representar objeto que se pierde en el decapado. - cambiar
_deepcopy_atomic
para asegurarsex
es estibables. Si no es así, devolver una instancia deLostObject
- objetos pueden definir métodos
__reduce__
y / o__reduce_ex__
para proporcionar pista acerca de si y cómo el decapado de ella. Nos aseguramos de que estos métodos no tirarán excepción de proporcionar indicio de que no puede ser decapada. - para evitar hacer copia innecesaria de objeto grande ( a la deepcopy real), que forma recursiva comprobar si un objeto es estibables, y sólo hacen parte unpicklable. Por ejemplo, para una tupla de una lista estibables y unpickable y un objeto, vamos a hacer una copia de la tupla - sólo el contenedor -. Pero no su lista de miembros
El siguiente es el diff:
[~/Development/scratch/] $ diff -uN /System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/copy.py mcopy.py
--- /System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/copy.py 2010-01-09 00:18:38.000000000 -0800
+++ mcopy.py 2010-11-10 08:50:26.000000000 -0800
@@ -157,6 +157,13 @@
cls = type(x)
+ # if x is picklable, there is no need to make a new copy, just ref it
+ try:
+ dumps(x)
+ return x
+ except TypeError:
+ pass
+
copier = _deepcopy_dispatch.get(cls)
if copier:
y = copier(x, memo)
@@ -179,10 +186,18 @@
reductor = getattr(x, "__reduce_ex__", None)
if reductor:
rv = reductor(2)
+ try:
+ x.__reduce_ex__()
+ except TypeError:
+ rv = LostObject, tuple()
else:
reductor = getattr(x, "__reduce__", None)
if reductor:
rv = reductor()
+ try:
+ x.__reduce__()
+ except TypeError:
+ rv = LostObject, tuple()
else:
raise Error(
"un(deep)copyable object of type %s" % cls)
@@ -194,7 +209,12 @@
_deepcopy_dispatch = d = {}
+from pickle import dumps
+class LostObject(object): pass
def _deepcopy_atomic(x, memo):
+ try:
+ dumps(x)
+ except TypeError: return LostObject()
return x
d[type(None)] = _deepcopy_atomic
d[type(Ellipsis)] = _deepcopy_atomic
Ahora, de vuelta a la parte de decapado. Sólo tiene que hacer una deepcopy el uso de esta nueva función deepcopy
y luego PIckle la copia. Las partes unpicklable se han eliminado durante el proceso de copia.
x = dict(a=1)
xx = dict(x=x)
x['xx'] = xx
x['f'] = file('/tmp/1', 'w')
class List():
def __init__(self, *args, **kwargs):
print 'making a copy of a list'
self.data = list(*args, **kwargs)
x['large'] = List(range(1000))
# now x contains a loop and a unpickable file object
# the following line will throw
from pickle import dumps, loads
try:
dumps(x)
except TypeError:
print 'yes, it throws'
def check_picklable(x):
try:
dumps(x)
except TypeError:
return False
return True
class LostObject(object): pass
from mcopy import deepcopy
# though x has a big List object, this deepcopy will not make a new copy of it
c = deepcopy(x)
dumps(c)
cc = loads(dumps(c))
# check loop refrence
if cc['xx']['x'] == cc:
print 'yes, loop reference is preserved'
# check unpickable part
if isinstance(cc['f'], LostObject):
print 'unpicklable part is now an instance of LostObject'
# check large object
if loads(dumps(c))['large'].data[999] == x['large'].data[999]:
print 'large object is ok'
Aquí está la salida:
making a copy of a list
yes, it throws
yes, loop reference is preserved
unpicklable part is now an instance of LostObject
large object is ok
Usted ve que 1) los punteros de inversión (entre x
y xx
) se conservan y que no se ejecutan en bucle infinito; 2) el objeto de archivo unpicklable es convertida a una instancia LostObject
; y 3) no es nueva copia del objeto grande se crea ya que es estibables.