Python: le Décapage d'un dict avec certains éléments unpicklable
j'ai un objet gui_project
qui a un attribut .namespace
, qui est un espace de noms dict. (c'est à dire une dict à partir de chaînes à des objets.)
(utilisé dans un programme semblable à un IDE pour permettre à l'utilisateur de définir son propre objet dans un shell Python.)
je veux choisir ce gui_project
, ainsi que l'Espace-nom. Le problème est que certains objets dans l'espace de noms (c'est-à-dire les valeurs du dict .namespace
) ne sont pas des objets sélectionnables. Par exemple, certains d'entre eux se référer aux widgets wxPython.
j'aimerais filtrer les objets impraticables, c'est-à-dire les exclure de la version pickled.
Comment faire?
(une chose que j'ai essayé est d'aller un par un sur les valeurs et d'essayer de les pickle, mais une certaine récursion infinie s'est produite, et je dois être à l'abri de cela.)
(Je ne mettre en œuvre une méthode GuiProject.__getstate__
en ce moment, pour se débarrasser d'autres choses impraticables en plus namespace
.)
5 réponses
j'utiliserais le support documenté de pickler pour les références d'objets persistants. Objet persistant références sont des objets qui sont référencés par la saumure, mais non conservés dans la saumure.
http://docs.python.org/library/pickle.html#pickling-and-unpickling-external-objects
ZODB utilise cette API depuis des années, elle est donc très stable. Lors de la décompression, vous pouvez remplacer les références de l'objet par tout ce que vous voulez. Dans votre cas, vous voudriez remplacer les références d'objet par des marqueurs indiquant que les objets ne pouvaient pas être décapés.
vous pourriez commencer par quelque chose comme ceci (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()
alors il suffit d'appeler dump_filtered() et load_filtered () au lieu de pickle.dump() et de la saumure.charge.)( les objets wxPython seront remplacés par des objets FilteredObjects au moment du déballage.
vous pourriez faire le solution plus générique en filtrant les objets qui ne sont pas des types intégrés et qui n'ont pas de méthode __getstate__
.
Update (15 Nov 2010): voici une façon d'obtenir la même chose avec les classes d'emballage. En utilisant les classes wrapper au lieu des 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
C'est comme ça que je ferais ceci (j'ai fait quelque chose de similaire avant et ça a fonctionné):
- écrire une fonction qui détermine si un objet est décapable ou non
- faire une liste de toutes les variables de sélection, basé sur la fonction ci-dessus
- faire un nouveau dictionnaire (appelé D) qui stocke toutes les variables non-pickleable
- pour chaque variable en D (cela ne fonctionne que si vous avez des variables d) faites une liste des chaînes, où chaque chaîne est du code python légal, tel que quand toutes ces chaînes sont exécutées dans l'ordre, vous obtenez la variable désirée
maintenant, quand vous unpickle, vous obtenez en arrière toutes les variables qui étaient à l'origine pickleable. Pour toutes les variables qui n'étaient pas sélectionnables, vous avez maintenant une liste de chaînes (code python légal) qui, lorsqu'elles sont exécutées dans l'ordre, vous donne la variable désirée.
Espérons que cette aide
j'ai fini par coder ma propre solution, en utilisant L'approche de Shane Hathaway.
voici le code . (Cherchez CutePickler
et CuteUnpickler
.) Voici les tests . Il fait partie de GarlicSim , donc vous pouvez l'utiliser par installation garlicsim
et faire from garlicsim.general_misc import pickle_tools
.
si vous voulez l'utiliser sur le code Python 3, Utilisez le Python 3 fourche de garlicsim
.
une approche serait d'hériter de pickle.Pickler
, et de remplacer la méthode save_dict()
. Copiez-le de la classe de base, qui lit comme ceci:
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, Passez un itérateur qui filtre tous les articles que vous ne voulez pas jeter, E. g
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))
comme save_dict n'est pas une API officielle, vous devez vérifier pour chaque nouvelle version de Python si cette option est toujours correcte.
la partie filtrante est en effet délicate. En utilisant des trucs simples, vous pouvez facilement obtenir le cornichon à travailler. Cependant, vous pourriez finir par filtrer trop et perdre des informations que vous pourriez garder lorsque le filtre semble un peu plus profond. Mais la vaste possibilité de choses qui peuvent finir dans le .namespace
rend difficile la construction d'un bon filtre.
Cependant, nous pourrions tirer parti de pièces qui font déjà partie de Python, comme deepcopy
dans le copy
module.
j'ai fait une copie du stock copy
module, et fait les choses suivantes:
- créer un nouveau type appelé
LostObject
pour représenter un objet qui sera perdu dans pickling. - modifier
_deepcopy_atomic
pour s'assurer quex
est sélectionnable. Si ce n'est pas le cas, retournez une instance deLostObject
Les objets - peuvent définir les méthodes
__reduce__
et / ou__reduce_ex__
pour fournir une indication si, et comment, à mariner. Nous nous assurons que ces méthodes ne vont pas jeter l'exception pour fournir une indication qu'il ne peut pas être décapé. - pour éviter de faire une copie inutile du grand objet ( a la deepcopy réelle), nous vérifions récursivement si un objet est picklable, et ne faire partie unpicklable. Par exemple, pour un tuple d'une liste de sélection et d'un objet non cliquable, nous ferons une copie du tuple-juste le conteneur - mais pas son membre liste.
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
maintenant retour à la partie de décapage. Il vous suffit de faire une copie en profondeur en utilisant cette nouvelle fonction deepcopy
et ensuite de décaper la copie. Les parties impraticables ont été enlevées pendant le 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) les indicateurs mutuels (entre x
et xx
) sont préservé et nous ne courons pas en boucle infinie; 2) l'objet fichier non cliquable est converti en une instance LostObject
; et 3) Pas de nouvelle copie du grand objet est créé car il est sélectionnable.