Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/mercurial/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python:用一些不可粘贴的项目来酸洗dict_Python_Pickle_Graceful Degradation - Fatal编程技术网

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

我复制了stock
copy
模块,并执行了以下操作:

  • 创建名为
    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,还要验证不同的实现),我不想要求想要pickle
    gui_项目的人使用这样的定制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