Python: мариновать диктовку непобедимыми предметами

У меня есть объект gui_project который имеет атрибут .namespace, который является пространством имен dict. (то есть диктат от строк до объектов.)

(Это используется в IDE-подобной программе, чтобы позволить пользователю определять свой собственный объект в оболочке Python.)

Я хочу засолить это gui_projectвместе с пространством имен. Проблема в том, что некоторые объекты в пространстве имен (то есть значения .namespace dict) не являются маринованными предметами. Например, некоторые из них ссылаются на виджеты wxPython.

Я бы хотел отфильтровать необратимые объекты, то есть исключить их из маринованной версии.

Как я могу это сделать?

(Одна вещь, которую я попытался, это перейти по одному к значениям и попытаться их перебрать, но произошла некоторая бесконечная рекурсия, и я должен быть в безопасности от этого.)

(Я реализую GuiProject.__getstate__ метод прямо сейчас, чтобы избавиться от других непростительных вещей, кроме namespace.)

5 ответов

Решение

В итоге я написал свое собственное решение, используя подход Шейна Хэтэуэя.

Вот код(Ищу CutePickler а также CuteUnpickler.) Вот тесты. Это часть GarlicSim, так что вы можете использовать его, установивgarlicsim и делать from garlicsim.general_misc import pickle_tools,

Если вы хотите использовать его в коде Python 3, используйте форк Python 3garlicsim,

Я бы использовал документированную поддержку выбора для постоянных ссылок на объекты. Постоянные ссылки на объекты - это объекты, на которые ссылается указатель, но которые не сохраняются в указателе.

http://docs.python.org/library/pickle.html

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 будут выбираться как постоянные идентификаторы, которые будут заменены на FilteredObjects во время удаления.

Вы можете сделать решение более общим, отфильтровывая объекты, которые не имеют встроенных типов и не имеют __getstate__ метод.

Обновление (15 ноября 2010 г.): вот способ добиться того же с помощью классов-оболочек. Используя классы-оболочки вместо подклассов, можно оставаться в документированном 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, вам необходимо проверять для каждой новой версии Python, является ли это переопределение все еще правильным.

Фильтрующая часть действительно хитрая. Используя простые приемы, вы можете легко заставить рассол работать. Однако вы можете отфильтровать слишком много и потерять информацию, которую можете сохранить, когда фильтр выглядит немного глубже. Но огромные возможности вещей, которые могут в конечном итоге в .namespace затрудняет создание хорошего фильтра.

Тем не менее, мы могли бы использовать части, которые уже являются частью Python, такие как deepcopy в copy модуль.

Я сделал копию акции copy модуль, и сделал следующие вещи:

  1. создать новый тип с именем LostObject представлять объект, который будет потерян при травлении.
  2. менять _deepcopy_atomic Чтобы убедиться x маринованные Если это не так, верните экземпляр LostObject
  3. объекты могут определять методы __reduce__ и / или __reduce_ex__ дать подсказку о том, стоит ли и как это мариновать. Мы уверены, что эти методы не будут генерировать исключение, чтобы дать подсказку, что оно не может быть обработано.
  4. чтобы избежать создания ненужной копии большого объекта (например, глубокой копии), мы рекурсивно проверяем, является ли объект доступным для выбора, и делаем только необрабатываемую часть. Например, для кортежа из списка с возможностью выбора и без возможности выбора объекта мы сделаем копию кортежа - просто контейнера - но не его списка членов.

Ниже приводится разница:

[~/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) взаимные указатели (между x а также xx) сохраняются и мы не сталкиваемся с бесконечным циклом; 2) необрабатываемый файловый объект преобразуется в LostObject пример; и 3) не создается новая копия большого объекта, так как он может быть выбран.

Другие вопросы по тегам