代码之家  ›  专栏  ›  技术社区  ›  Ram Rachum

Python:用一些不可擦的东西来腌制dict

  •  11
  • Ram Rachum  · 技术社区  · 14 年前

    gui_project 有属性的 .namespace

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

    我想腌制这个 贵项目 。命名空间

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

    (我试过的一件事是一个接一个地对值进行筛选,但是发生了一些无限的递归,我需要避免这种情况。)

    (我确实实施了 GuiProject.__getstate__ 现在的方法,除了 namespace .)

    5 回复  |  直到 14 年前
        1
  •  6
  •   Shane Hathaway    14 年前

    我将使用pickler对持久对象引用的文档化支持。持久对象引用是由pickle引用但不存储在pickle中的对象。

    http://docs.python.org/library/pickle.html#pickling-and-unpickling-external-objects

    你可以这样开始(未经测试):

    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对象将被pickle为持久id,在取消pickling时将被FilteredObjects替换。

    __getstate__ 方法。

    更新

    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
    
        2
  •  1
  •   Tim McNamara    14 年前

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

    1. 根据上面的函数,列出所有可pickle变量
    2. 创建一个新的字典(称为D),存储所有不可pickle的变量
    3. 对于D中的每个变量(只有在D中有非常相似的变量时才有效) 列一个字符串列表,其中每个字符串都是合法的python代码,这样 当所有这些字符串都按顺序执行时,您将获得所需的变量

    希望这有帮助

        3
  •  1
  •   Ram Rachum    14 年前

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

    Here's the code CutePickler CuteUnpickler .) Here are the tests GarlicSim ,所以你可以通过 installing garlicsim 和做 from garlicsim.general_misc import pickle_tools

    如果要在Python3代码中使用它,请使用 Python 3 fork of garlicsim .

        4
  •  0
  •   Martin v. Löwis    14 年前

    一种方法是继承 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())
    

    但是,在批处理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版本是否仍然正确。

        5
  •  0
  •   eddie_c    14 年前

    过滤部分确实很棘手。使用简单的技巧,你可以很容易地得到腌菜的工作。但是,你可能会过滤掉太多的内容,并且在过滤器看起来更深一点的时候丢失了你可以保留的信息。但最终可能发生的事情 .namespace

    但是,我们可以利用已经是Python一部分的部分,比如 deepcopy copy 模块。

    我复印了一份股票 模块,并执行以下操作:

    1. 创建名为 LostObject 表示将在酸洗过程中丢失的物体。
    2. _deepcopy_atomic 以确保 x 是可挑选的。如果不是,则返回
    3. 对象可以定义方法 __reduce__ 和/或 __reduce_ex__ 提供是否以及如何腌制的提示。我们确保这些方法不会抛出异常,以提供无法对其进行pickle的提示。
    4. 洛杉矶 实际的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
    

    现在回到酸洗部分。你只要用这个新的 深度复制

    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)相互的指针 xx )是保留的,我们不会进入无限循环;2)不可拾取的文件对象被转换为 实例;和3)不会创建大对象的新副本,因为它是可拾取的。