Python:序列化包含不可序列化项的字典
我有一个对象 gui_project
,它有一个属性 .namespace
,这个属性是一个命名空间字典。也就是说,它是一个把字符串映射到对象的字典。
这个字典在一个类似IDE的程序中使用,允许用户在Python环境中定义自己的对象。
我想把这个 gui_project
和它的命名空间一起保存下来,但问题是,命名空间中的一些对象(也就是 .namespace
字典里的值)是无法被保存的。例如,有些对象是wxPython的控件。
我想过滤掉那些无法保存的对象,也就是在保存的时候把它们排除在外。
我该怎么做呢?
我尝试过逐个检查这些值,看能不能保存它们,但出现了无限递归的问题,我需要避免这种情况。
目前我已经实现了一个 GuiProject.__getstate__
方法,用来去掉除了 namespace
之外的其他无法保存的东西。
5 个回答
这是我会这样做的(我之前做过类似的事情,而且成功了):
- 写一个函数,用来判断一个对象是否可以被“腌制”(pickleable)。
- 根据上面的函数,列出所有可以被“腌制”的变量。
- 创建一个新的字典(叫做D),用来存储所有不能被“腌制”的变量。
- 对于字典D中的每个变量(这个方法适用于你有很多相似变量的情况), 创建一个字符串列表,每个字符串都是合法的Python代码,这样当这些字符串按顺序执行时,就能得到你想要的变量。
现在,当你进行“解腌制”时,你会得到所有最初可以被“腌制”的变量。对于那些不能被“腌制”的变量,你现在有一个字符串列表(合法的Python代码),按顺序执行这些代码,就能得到你想要的变量。
希望这对你有帮助。
我会使用pickler提供的持久对象引用功能。持久对象引用是指那些在pickle中被引用但并没有被实际存储在pickle里的对象。
http://docs.python.org/library/pickle.html#pickling-and-unpickling-external-objects
ZODB已经使用这个API很多年了,所以它非常稳定。在反序列化的时候,你可以用任何你想要的东西来替换对象引用。在你的情况下,你可能想用一些标记来替代那些无法被序列化的对象引用。
你可以从这样的代码开始(未经测试):
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进行序列化,在反序列化时再替换成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
我最后自己写了一个解决方案,参考了Shane Hathaway的方法。
这是代码。(你可以找找 CutePickler
和 CuteUnpickler
。)这里是测试代码。这个代码是 GarlicSim 的一部分,所以你可以通过 安装 garlicsim
来使用它,然后用 from garlicsim.general_misc import pickle_tools
来导入。
如果你想在Python 3的代码中使用它,可以用 garlicsim的Python 3版本。