我有一个对象gui_project
,它有一个属性.namespace
,它是一个命名空间字典。 (即从字符串到对象的字典。)
(这在类似IDE的程序中用于让用户在Python shell中定义自己的对象。)
我想将这个gui_project
和命名空间一起腌制。问题是,命名空间中的某些对象(即.namespace
dict的值)不是可选对象。例如,其中一些是指wxPython小部件。
我想过滤掉不可剔除的对象,也就是说,将它们从腌制版本中排除。
我该怎么做?
(我尝试过的一件事就是逐一对价值进行尝试并尝试腌制它们,但是发生了一些无限递归,我需要对此保证安全。)
(我现在实现了GuiProject.__getstate__
方法,以除去namespace
以外的其他无法解决的内容。)
答案 0 :(得分:6)
我会使用pickler对持久对象引用的文档支持。持久对象引用是由pickle引用但不存储在pickle中的对象。
http://docs.python.org/library/pickle.html#pickling-and-unpickling-external-objects
ZODB多年来一直使用这个API,所以非常稳定。 unpickling时,您可以用您喜欢的任何内容替换对象引用。在您的情况下,您可能希望用标记替换对象引用,指示无法对对象进行pickle。
你可以从这样的事情开始(未经测试):
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()
然后只调用dump_filtered()和load_filtered()而不是pickle.dump()和pickle.load()。 wxPython对象将作为持久性ID进行pickle,在unpickling时将被FilteredObjects替换。
您可以通过过滤掉不属于内置类型且没有__getstate__
方法的对象来使解决方案更通用。
更新(2010年11月15日):这是一种使用包装类实现相同功能的方法。使用包装类而不是子类,可以保留在记录的API中。
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
答案 1 :(得分:1)
这就是我要这样做的方式(之前我做了类似的事情并且有效):
现在,当你进行unpickle时,你会收回所有最初可选择的变量。对于所有不可拾取的变量,您现在有一个字符串列表(合法的python代码),按顺序执行时,会为您提供所需的变量。
希望这有帮助
答案 2 :(得分:1)
我最终使用Shane Hathaway的方法编写了我自己的解决方案。
Here's the code。 (查找CutePickler
和CuteUnpickler
。)Here are the tests。这是GarlicSim的一部分,因此您可以installing garlicsim
使用它并执行from garlicsim.general_misc import pickle_tools
。
如果要在Python 3代码上使用它,请使用Python 3 fork of garlicsim
。
答案 3 :(得分:0)
一种方法是从pickle.Pickler
继承,并覆盖save_dict()
方法。从基类复制它,如下所示:
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())
但是,在_batch_setitems中,传递一个迭代器来过滤掉你不想转储的所有项目,例如
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不是官方API,因此您需要检查每个新的Python版本是否仍然正确。
答案 4 :(得分:0)
过滤部分确实很棘手。使用简单的技巧,您可以轻松地使泡菜工作。但是,当过滤器看起来更深一些时,您可能最终过滤掉过多而丢失可能保留的信息。但是,.namespace
中可能出现的最大可能性使得构建良好的过滤器变得困难。
但是,我们可以利用已经属于Python的部分,例如deepcopy
模块中的copy
。
我制作了库存copy
模块的副本,并做了以下事情:
LostObject
的新类型来表示将在酸洗中丢失的对象。_deepcopy_atomic
以确保x
可以选择。如果不是,请返回LostObject
__reduce__
和/或__reduce_ex__
,以提供有关是否以及如何挑选它的提示。我们确保这些方法不会抛出异常,以提示它不能被腌制。以下是差异:
[~/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
现在回到酸洗部分。您只需使用这个新的deepcopy
函数进行深度复制,然后选择复制。在复制过程中,不可拆除的部分已被删除。
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'
这是输出:
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
你看到1)相互指针(x
和xx
之间)被保留,我们不会遇到无限循环; 2)将unpicklable文件对象转换为LostObject
实例; 3)因为它是可选择的,所以不会创建大对象的新副本。