Python:用一些不可打击的物品腌制一个字典

时间:2010-11-02 18:04:50

标签: python pickle graceful-degradation

我有一个对象gui_project,它有一个属性.namespace,它是一个命名空间字典。 (即从字符串到对象的字典。)

(这在类似IDE的程序中用于让用户在Python shell中定义自己的对象。)

我想将这个gui_project和命名空间一起腌制。问题是,命名空间中的某些对象(即.namespace dict的值)不是可选对象。例如,其中一些是指wxPython小部件。

我想过滤掉不可剔除的对象,也就是说,将它们从腌制版本中排除。

我该怎么做?

(我尝试过的一件事就是逐一对价值进行尝试并尝试腌制它们,但是发生了一些无限递归,我需要对此保证安全。)

(我现在实现了GuiProject.__getstate__方法,以除去namespace以外的其他无法解决的内容。)

5 个答案:

答案 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)

这就是我要这样做的方式(之前我做了类似的事情并且有效):

  1. 编写一个确定对象是否可拾取的函数
  2. 根据上述功能
  3. 列出所有可选择的变量
  4. 创建一个存储所有非pickle变量的新词典(称为D)
  5. 对于D中的每个变量(仅当d中的变量非常相似时才有效)    制作一个字符串列表,其中每个字符串都是合法的python代码,这样    当所有这些字符串按顺序执行时,您将获得所需的变量
  6. 现在,当你进行unpickle时,你会收回所有最初可选择的变量。对于所有不可拾取的变量,您现在有一个字符串列表(合法的python代码),按顺序执行时,会为您提供所需的变量。

    希望这有帮助

答案 2 :(得分:1)

我最终使用Shane Hathaway的方法编写了我自己的解决方案。

Here's the code。 (查找CutePicklerCuteUnpickler。)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模块的副本,并做了以下事情:

  1. 创建一个名为LostObject的新类型来表示将在酸洗中丢失的对象。
  2. 更改_deepcopy_atomic以确保x可以选择。如果不是,请返回LostObject
  3. 的实例
  4. 对象可以定义方法__reduce__和/或__reduce_ex__,以提供有关是否以及如何挑选它的提示。我们确保这些方法不会抛出异常,以提示它不能被腌制。
  5. 为了避免对大对象进行不必要的复制( la 实际的深度复制),我们递归检查一个对象是否可以被选中,并且只生成不可篡改的部分。例如,对于可选列表的元组和不可检索的对象,我们将复制元组 - 只是容器 - 而不是其成员列表。
  6. 以下是差异:

    [~/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)相互指针(xxx之间)被保留,我们不会遇到无限循环; 2)将unpicklable文件对象转换为LostObject实例; 3)因为它是可选择的,所以不会创建大对象的新副本。