Python:序列化包含不可序列化项的字典

12 投票
5 回答
7738 浏览
提问于 2025-04-16 06:26

我有一个对象 gui_project,它有一个属性 .namespace,这个属性是一个命名空间字典。也就是说,它是一个把字符串映射到对象的字典。

这个字典在一个类似IDE的程序中使用,允许用户在Python环境中定义自己的对象。

我想把这个 gui_project 和它的命名空间一起保存下来,但问题是,命名空间中的一些对象(也就是 .namespace 字典里的值)是无法被保存的。例如,有些对象是wxPython的控件。

我想过滤掉那些无法保存的对象,也就是在保存的时候把它们排除在外。

我该怎么做呢?

我尝试过逐个检查这些值,看能不能保存它们,但出现了无限递归的问题,我需要避免这种情况。

目前我已经实现了一个 GuiProject.__getstate__ 方法,用来去掉除了 namespace 之外的其他无法保存的东西。

5 个回答

1

这是我会这样做的(我之前做过类似的事情,而且成功了):

  1. 写一个函数,用来判断一个对象是否可以被“腌制”(pickleable)。
  2. 根据上面的函数,列出所有可以被“腌制”的变量。
  3. 创建一个新的字典(叫做D),用来存储所有不能被“腌制”的变量。
  4. 对于字典D中的每个变量(这个方法适用于你有很多相似变量的情况), 创建一个字符串列表,每个字符串都是合法的Python代码,这样当这些字符串按顺序执行时,就能得到你想要的变量。

现在,当你进行“解腌制”时,你会得到所有最初可以被“腌制”的变量。对于那些不能被“腌制”的变量,你现在有一个字符串列表(合法的Python代码),按顺序执行这些代码,就能得到你想要的变量。

希望这对你有帮助。

7

我会使用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
1

我最后自己写了一个解决方案,参考了Shane Hathaway的方法。

这是代码。(你可以找找 CutePicklerCuteUnpickler。)这里是测试代码。这个代码是 GarlicSim 的一部分,所以你可以通过 安装 garlicsim 来使用它,然后用 from garlicsim.general_misc import pickle_tools 来导入。

如果你想在Python 3的代码中使用它,可以用 garlicsim的Python 3版本

撰写回答