Question

J'ai un gui_project objet qui a une .namespace d'attribut, qui est un espace de noms dict. (À savoir un dict de cordes à objets.)

(Ceci est utilisé dans un programme comme IDE pour l'utilisateur à définir son propre objet dans un shell Python.)

Je veux décaper cette gui_project, ainsi que l'espace de noms. Le problème est, certains objets dans l'espace de noms (à savoir les valeurs de la dict .namespace) ne sont pas des objets picklable. Par exemple, certains d'entre eux se réfèrent à des widgets wxPython.

Je voudrais filtrer les objets unpicklable, qui est, ce qui les exclut de la version marinée.

Comment faire?

(Une chose que j'ai essayé est d'aller un par un sur les valeurs et essayer de les décaper, mais certains récursion infinie est arrivé, et je dois être sûr de cela.)

(je fais mettre en œuvre une méthode de GuiProject.__getstate__ en ce moment, pour se débarrasser d'autres choses en plus unpicklable namespace.)

Était-ce utile?

La solution 3

J'ai fini par coder ma propre solution à cela, en utilisant l'approche de Shane Hathaway.

Voici code. (Cherchez CutePickler et CuteUnpickler.) ici sont les tests . Cela fait partie de GarlicSim , de sorte que vous pouvez l'utiliser par installation garlicsim et faire from garlicsim.general_misc import pickle_tools.

Si vous voulez utiliser le code Python 3, utilisez le Python 3 fork de garlicsim .

Autres conseils

J'utiliser le soutien documenté pour les références d'objets persistants du Pickler. références d'objets persistants sont des objets qui sont référencés par les conserves au vinaigre mais pas stocké dans le cornichon.

http://docs.python.org/ bibliothèque / pickle.html # décapage et unpickling-objets-externe

ZODB a utilisé cette API pendant des années, il est donc très stable. Lorsque unpickling, vous pouvez remplacer les références d'objets avec tout ce que vous aimez. Dans votre cas, vous voulez remplacer les références d'objet avec des marqueurs indiquant que les objets ne peuvent pas être décapées.

Vous pourriez commencer par quelque chose comme ça (non testé):

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

Ensuite il suffit d'appeler dump_filtered () et load_filtered () au lieu de pickle.dump () et pickle.load (). objets wxPython seront décapés comme ID persistants, être remplacés par FilteredObjects à unpickling temps.

Vous pouvez faire la solution plus générique en filtrant les objets qui ne sont pas des types intégrés et ont aucune méthode __getstate__.

Mise à jour (15 novembre 2010): Voici un moyen de réaliser la même chose avec des classes wrapper. En utilisant les classes wrapper au lieu de sous-classes, il est possible de rester dans l'API documentée.

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

Voici comment je ferais cela (je l'ai fait quelque chose de semblable avant et ça a marché):

  1. Ecrire une fonction qui détermine si oui ou non un objet est pickleable
  2. Faites une liste de toutes les variables pickleable, en fonction de la fonction ci-dessus
  3. un nouveau dictionnaire (appelé D) qui stocke toutes les variables non pickleable
  4. Pour chaque variable D (cela ne fonctionne que si vous avez des variables très similaires à d)    faire une liste de chaînes, où chaque chaîne est un code python juridique, de sorte que    lorsque toutes ces chaînes sont exécutées dans l'ordre, vous obtenez la variable souhaitée

Maintenant, quand vous unpickle, vous revenez toutes les variables qui étaient à l'origine pickleable. Pour toutes les variables qui ne sont pas pickleable, vous avez maintenant une liste de chaînes (code python légal) que lorsqu'il est exécuté dans l'ordre, vous donne la variable souhaitée.

Hope this helps

Une approche serait d'hériter de pickle.Pickler, et remplacer la méthode de save_dict(). Copiez-le à partir de la classe de base, qui se lit comme suit:

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

Cependant, dans les _batch_setitems, passer un itérateur qui filtre tous les éléments que vous ne voulez pas l'objet de dumping, par exemple

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

save_dict n'est pas une API officielle, vous devez vérifier pour chaque nouvelle version Python si cette dérogation est toujours correcte.

La partie de filtrage est en effet délicat. En utilisant des astuces simples, vous pouvez facilement obtenir le cornichon au travail. Cependant, vous pourriez finir par filtrage trop et perdre des informations que vous pouvez conserver lorsque le filtre a l'air un peu plus profond. Mais la possibilité vaste de choses qui peuvent se retrouver dans le .namespace rend la construction d'un bon filtre difficile.

Cependant, nous avons pu tirer parti de pièces qui font déjà partie de Python, comme deepcopy dans le module copy.

J'ai fait une copie du module copy stock, et a fait les choses suivantes:

  1. créer un nouveau type nommé pour représenter LostObject objet qui sera perdu dans le décapage.
  2. changement _deepcopy_atomic pour vous assurer que x est picklable. Si ce n'est pas, retourner une instance de LostObject
  3. Les objets peuvent définir des méthodes __reduce__ et / ou de fournir __reduce_ex__ indication de savoir si et comment il décaper. Nous veillons à ce que ces méthodes ne jetteront pas exception à fournir indice qu'il ne peut pas être décapée.
  4. pour éviter de faire une copie inutile de gros objet ( a la deepcopy réelle), nous vérifions récursive si un objet est picklable, et ne font partie unpicklable. Par exemple, pour un tuple d'une liste picklable et et un objet unpickable, nous ferons une copie du tuple - tout le contenant -. Mais pas la liste des membres

Ce qui suit est la 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

Revenons maintenant à la partie de décapage. Vous faites simplement un deepcopy en utilisant cette nouvelle fonction deepcopy puis Pickle la copie. Les parties unpicklable ont été supprimés au cours du processus de copie.

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'

Voici la sortie:

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

Vous voyez que 1) des pointeurs (entre mutuelles x et xx) sont préservés et nous ne fonctionnent pas dans la boucle infinie; 2) l'objet fichier unpicklable est converti en une instance de LostObject; et 3) pas nouvelle copie du grand objet est créé, car il est picklable.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top