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

¿Fue útil?

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ó):

  1. escribir una función que determina si o no un objeto es pickleable
  2. Haga una lista de todas las variables pickleable, basado en la función anterior
  3. Crear un nuevo diccionario (llamado D) que almacena todas las variables no pickleable
  4. 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:

  1. crear un nuevo tipo llamado LostObject para representar objeto que se pierde en el decapado.
  2. cambiar _deepcopy_atomic para asegurarse x es estibables. Si no es así, devolver una instancia de LostObject
  3. 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.
  4. 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.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top