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

StackOverflow https://stackoverflow.com/questions/4080688

Вопрос

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

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

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

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

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

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

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

Это было полезно?

Решение 3

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

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

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

Другие советы

Я бы использовал документированную поддержку Pickler для постоянных объектов. Настоящие ссылки на постоянные объекты являются объектами, которые ссылаются на сортировку, но не хранятся в сочине.

http://docs.cython.org/library/pickle.html#pickling-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 () вместо sickle.dump () и pickle.load (). Объекты WXPYPHON будут замариваться как настойчивые идентификаторы, которые должны быть заменены фильтратообъектами при запуске.

Вы можете сделать решение более универсальным путем отфильтровывания объектов, которые не являются встроенными типами и не имеют __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_setiteTems, передайте итератор, который фильтрует все элементы, которые вы не хотите быть сброшенным, например,

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) не новая копия большого объекта создается с тех пор, как она подбирается.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top