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

(这是在类似IDE的程序中使用的,以使用户在Python shell中定义自己的对象。)

我想腌制这个 gui_project, ,以及名称空间。问题是,名称空间中的某些对象(即 .namespace dict)不是挑剔的对象。例如,其中一些是指wxpython小部件。

我想滤除不挑选的对象,也就是说,将它们排除在腌制版本之外。

我怎样才能做到这一点?

(我尝试过的一件事是一一逐一地对其进行腌制,但是发生了一些无限的递归,我需要从中安全。)

(我确实实施了 GuiProject.__getstate__ 现在的方法,除了除了其他不可挑选的东西 namespace.)

有帮助吗?

解决方案 3

我最终使用Shane Hathaway的方法对此进行了编码。

这是代码. 。 (寻找 CutePicklerCuteUnpickler.) 这是测试. 。这是 Garlicsim, ,因此您可以使用它 安装 garlicsim 并做 from garlicsim.general_misc import pickle_tools.

如果要在Python 3代码上使用它,请使用 Python 3叉 garlicsim.

其他提示

我将使用Pickler记录的对持续对象引用的支持。持续的对象引用是泡菜引用但未存储在泡菜中的对象。

http://docs.python.org/library/pickle.html#pickling-and-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,在未点缀时将用过滤器代替。

您可以通过滤除非内置类型的对象而使解决方案更加通用 __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. 编写一个确定对象是否可挑选的函数
  2. 根据上述功能列出所有可挑选变量的列表
  3. 制作一个新的词典(称为d),以存储所有不可挑选变量
  4. 对于d中的每个变量(仅在D中有非常相似的变量时,此字符串列表,每个字符串都是合法的python代码,因此,当所有这些字符串按顺序执行时,您就会得到所需的变量

现在,当您取消挑剔时,您会收回最初可挑选的所有变量。对于所有不可拾取的变量,您现在有一个字符串列表(合法的python代码),在按顺序执行时,会为您提供所需的变量。

希望这可以帮助

一种方法是继承 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,因此您需要检查每个新版本是否仍然正确。

过滤部分确实很棘手。使用简单的技巧,您可以轻松地让泡菜工作。但是,您最终可能会过滤太多,并且丢失了当过滤器看起来更深一点时可以保留的信息。但是,事物的巨大可能性可能会在 .namespace 使构建良好的过滤器变得困难。

但是,我们可以利用已经是Python一部分的作品,例如 deepcopy 在里面 copy 模块。

我制作了股票的副本 copy 模块,并做了以下操作:

  1. 创建一个名称的新类型 LostObject 代表将在腌制中丢失的物体。
  2. 改变 _deepcopy_atomic 确保;确定 x 是可挑选的。如果不是,请返回一个实例 LostObject
  3. 对象可以定义方法 __reduce__ 和/或 __reduce_ex__ 提供有关是否以及如何腌制的提示。我们确保这些方法不会引发异常,以提供无法腌制的提示。
  4. 避免制作不必要的大对象的副本(la 实际的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 功能,然后腌制副本。在复制过程中,已删除了不可贴的零件。

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)不可贴的文件对象被转换为 LostObject 实例; 3)由于它是可挑选的,因此没有创建大对象的新副本。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top