Python: Beiz- ein dict mit einigen unpicklable Artikel
-
28-09-2019 - |
Frage
ich ein Objekt gui_project
haben, die ein Attribut .namespace
hat, die ein Namespace dict ist. (Das heißt ein dict von Strings zu Objekten.)
(Dies ist in einem IDE-ähnlichen Programm verwendet der Anwender festlegen, sein eigenes Objekt in einem Python-Shell zu lassen.)
Ich mag diese gui_project
beizen, zusammen mit dem Namespace. Das Problem ist, einige Objekte im Namensraum (das heißt Wert der .namespace
dict) nicht picklable Objekte. Zum Beispiel beziehen sich einige von ihnen zu WxPython Widgets.
Ich möchte die unpicklable Objekte herauszufiltern, das heißt, ausschließen sie von der eingelegten Version.
Wie kann ich das tun?
(Eine Sache habe ich versucht, ist eine nach der anderen auf den Werten zu gehen und versuchen, sie beizen, aber einige unendliche Rekursion passiert ist, und ich brauche aus, dass sicher zu sein.)
(I eine GuiProject.__getstate__
Methode tut implementieren gerade jetzt, von anderen unpicklable Sachen neben namespace
loszuwerden.)
Lösung 3
ich am Ende Codierung meine eigene Lösung für dieses Problem, mit Shane Hathaway Ansatz.
Hier ist der Code . (Suchen Sie nach CutePickler
und CuteUnpickler
.) Hier sind die Tests. Es ist Teil der GarlicSim , so dass Sie es von garlicsim
und tun from garlicsim.general_misc import pickle_tools
installieren.
Wenn Sie es auf Python 3-Code verwenden möchten, verwenden Sie die Python 3 Gabel von garlicsim
.
Andere Tipps
würde ich die pickler dokumentierten Unterstützung für persistente Objektreferenzen verwenden. Persistent Objektreferenzen sind Objekte, die durch die Beize referenziert werden, aber nicht in der Gurke gespeichert.
http://docs.python.org/ Bibliothek / pickle.html # Beizen-und-Unpickling-external-Objekte
hat ZODB diese API seit Jahren verwendet, so ist es sehr stabil ist. Wenn Unpickling, können Sie die Objektreferenzen mit etwas ersetzen Sie mögen. In Ihrem Fall würden Sie die Objektreferenzen mit Markern zu ersetzen, was anzeigt, dass die Objekte nicht gebeizt werden.
Sie können mit so etwas wie dies starten (ungetestet):
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()
Dann rufen Sie einfach dump_filtered () und load_filtered () anstelle von pickle.dump () und pickle.load (). WxPython Objekte werden als persistente IDs gebeizt werden, um Unpickling Zeit mit FilteredObjects ersetzt werden.
Sie könnte die Lösung genereller machen durch Herausfiltern von Objekten, die nicht von der eingebauten Typen sind und keine __getstate__
Methode.
Aktualisieren (15 Nov 2010): Hier ist ein Weg, um die gleiche Sache mit Wrapper-Klassen zu erreichen. Unter Verwendung von Wrapper-Klassen anstelle von Subklassen, ist es möglich, innerhalb der dokumentierten API zu bleiben.
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
Dies ist, wie ich dies tun würde (ich habe etwas ähnliches vor und es funktionierte):
- Schreiben Sie eine Funktion, die bestimmt, ob ein Objekt ist pickleable Make
- eine Liste aller pickleable Variablen, basierend auf der obigen Funktion
- Erstellen Sie ein neues Wörterbuch (so genannte D), dass speichert alle nicht-pickleable Variablen
- Für jede Variable in D (das funktioniert nur, wenn Sie sehr ähnliche Variablen in d haben) machen Sie eine Liste von Strings, wobei jeder String Recht Python-Code, so dass wenn alle diese Strings in dieser Reihenfolge ausgeführt werden, können Sie die gewünschte Variable erhalten
Wenn Sie nun unpickle, Sie alle Variablen zurück, die ursprünglich pickleable waren. Für alle Variablen, die nicht pickleable waren, haben Sie jetzt eine Liste von Strings (Rechtskode Python), dass, wenn in der Reihenfolge ausgeführt, gibt Ihnen die gewünschte Variable.
Hope, das hilft
wäre ein Ansatz von pickle.Pickler
zu erben sein, und die save_dict()
Methode außer Kraft setzen. Kopieren Sie es von der Basisklasse, die wie folgt lautet:
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())
Doch in der _batch_setitems, passiert ein Iterator, filtert alle Elemente, die Sie nicht wollen, abgeladen werden, z
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))
Als save_dict keine offizielle API ist, müssen Sie für jede neue Python-Version überprüfen, ob diese Überschreibung noch korrekt ist.
Die Filterung Teil ist in der Tat schwierig. Mit einfachen Tricks können Sie bequem die Gurke an der Arbeit. Sie könnten jedoch auch zu viel am Ende herausfiltert und Informationen zu verlieren, dass Sie halten könnten, wenn die Filter ein wenig tiefer aussehen. Aber die große Möglichkeit der Dinge, die in der .namespace
kann am Ende macht den Aufbau einer guten Filter schwierig.
Allerdings können wir Stücke nutzen, die bereits Teil von Python sind, wie deepcopy
im copy
Modul.
habe ich eine Kopie des Aktien copy
Modul und hat die folgenden Dinge:
- erstellen Sie einen neuen Typ namens
LostObject
Objekt darzustellen, die in Beizen verloren. - Änderung
_deepcopy_atomic
um sicherzustellen, dassx
picklable ist. Wenn es nicht, gibt eine Instanz vonLostObject
- Objekte können Methoden
__reduce__
und / oder__reduce_ex__
definieren Hinweis zu liefern, ob und wie es beizen. Wir stellen sicher, dass diese Methoden nicht Ausnahme werfen Hinweis zu liefern, dass es nicht gebeizt werden kann. - vermeiden unnötige Kopie des großen Objekts erzeugt werden ( a la tatsächliche deep), überprüfen wir rekursiv, ob ein Objekt picklable ist, und nur unpicklable Teil machen. Zum Beispiel für ein Tupel einer picklable Liste und und ein unpickable Objekt, werden wir eine Kopie des Tupels machen - nur um den Behälter -. Aber nicht seine Mitgliedsliste
Im Folgenden ist der Unterschied:
[~/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
Nun zurück zum Beizen Teil. Sie machen einfach ein deep diese neue deepcopy
-Funktion und dann die Kopie beizen. Die unpicklable Teile während des Kopiervorgangs entfernt wurden.
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'
Hier ist die Ausgabe:
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
Sie sehen, dass 1) die gegenseitigen Zeiger (zwischen x
und xx
) bleiben erhalten und wir nicht in Endlosschleife laufen; 2) das unpicklable Dateiobjekt wird eine LostObject
Instanz umgewandelt wird; und 3) nicht neue Kopie des großen Objekts erstellt wird, da es picklable ist.