Python:用一些不可粘贴的项目来酸洗dict
我有一个对象Python:用一些不可粘贴的项目来酸洗dict,python,pickle,graceful-degradation,Python,Pickle,Graceful Degradation,我有一个对象gui\u项目,它有一个属性。名称空间,它是一个名称空间dict(即从字符串到对象的dict) (这在类似IDE的程序中用于让用户在Python shell中定义自己的对象。) 我想将这个gui\u项目与名称空间一起pickle。问题是,命名空间中的某些对象(即.namespacedict的值)不是可拾取的对象。例如,其中一些引用wxPython小部件 我想过滤掉不可拼接的对象,也就是说,将它们从pickle版本中排除 我该怎么做 (有一件事我试着一个接一个地处理这些值,并尝试对它们
gui\u项目
,它有一个属性。名称空间
,它是一个名称空间dict(即从字符串到对象的dict)
(这在类似IDE的程序中用于让用户在Python shell中定义自己的对象。)
我想将这个gui\u项目与名称空间一起pickle。问题是,命名空间中的某些对象(即.namespace
dict的值)不是可拾取的对象。例如,其中一些引用wxPython小部件
我想过滤掉不可拼接的对象,也就是说,将它们从pickle版本中排除
我该怎么做
(有一件事我试着一个接一个地处理这些值,并尝试对它们进行pickle处理,但是发生了一些无限递归,我需要避免这种情况。)
(我现在确实实现了一个GuiProject.\uuuu getstate\uuuu
方法,以除去namespace
之外的其他不可粘贴的东西)一种方法是继承pickle.Pickler
,并重写save\u 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版本的覆盖是否仍然正确。过滤部分确实很棘手。使用简单的技巧,你可以很容易地让泡菜发挥作用。但是,您可能会过滤掉太多的内容,并丢失当过滤器看起来更深一点时可以保留的信息。但是,在.namespace
中可能出现的情况非常多,这使得构建一个好的过滤器非常困难
但是,我们可以利用已经是Python一部分的片段,例如copy
模块中的deepcopy
我复制了stockcopy
模块,并执行了以下操作:
创建名为LostObject
的新类型,以表示将在酸洗过程中丢失的对象
更改\u deepcopy\u atomic
以确保x
可拾取。如果不是,则返回LostObject
对象可以定义方法\uuuuuuuuuuuuuuuu
和/或\uuuuuuuuuuuuuuu reduce\uex\uuuuuuuuu
,以提供是否以及如何对其进行pickle的提示。我们确保这些方法不会抛出异常以提供无法对其进行pickle的提示
为了避免对大对象进行不必要的复制(一个实际的deepcopy),我们递归地检查对象是否可拾取,并且只生成不可拾取的部分。例如,对于一个可拾取列表和一个不可拾取对象的元组,我们将创建元组的副本—只是容器—而不是其成员列表
以下是区别:
[~/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
函数创建一个deepcopy,然后对副本进行pickle。在复制过程中,已移除不可粘贴的零件
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) 不可粘贴的文件对象被转换为LostObject
实例;3)不会创建大对象的新副本,因为它是可拾取的 我将使用pickler对持久对象引用的文档化支持。持久对象引用是由pickle引用但未存储在pickle中的对象
ZODB已经使用这个API很多年了,所以它非常稳定。取消勾选时,可以将对象引用替换为您喜欢的任何对象。在您的情况下,您可能希望使用指示对象无法被pickle的标记替换对象引用
您可以从以下内容开始(未经测试):
然后只调用dump_filtered()和load_filtered()而不是pickle.dump()和pickle.load()。wxPython对象将作为持久ID进行pickle,在取消pickle时替换为FilteredObject
您可以通过过滤掉不属于内置类型且没有\uu getstate\uu
方法的对象,使解决方案更通用
更新(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
我就是这样做的(我以前也做过类似的事情,但效果很好):
编写一个函数,确定对象是否可pickle
根据上述函数,列出所有可pickle变量
创建一个新的字典(称为D),其中存储所有不可pickle的变量
对于D中的每个变量(只有在D中有非常相似的变量时才有效)
制作一个字符串列表,其中每个字符串都是合法的python代码,这样
当所有这些字符串按顺序执行时,您将获得所需的变量
现在,当您取消pickle时,您将返回最初可pickle的所有变量。对于所有不可pickle的变量,您现在有一个字符串列表(合法的python代码),当按顺序执行时,这些字符串将为您提供所需的变量
希望这有帮助我最终用Shane Hathaway的方法编写了自己的解决方案
。(查找CutePickler
和CuteUpPickler
)。它是的一部分,因此您可以使用它并从garlicsim.general\u misc import pickle\u tools
执行
如果您想在Python 3代码上使用它,请使用。Hm,是否有更具可移植性的解决方案?由于save_dict
不是官方API(我不仅要验证不同版本的API,还要验证不同的实现),我不想要求想要picklegui_项目的人使用这样的定制pickler。如果我没有
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