Python: мариновать диктовку непобедимыми предметами - PullRequest
11 голосов
/ 02 ноября 2010

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

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

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

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

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

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

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

Ответы [ 5 ]

6 голосов
/ 11 ноября 2010

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

http://docs.python.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 () вместо pickle.dump () и pickle.load ().Объекты wxPython будут выбираться как постоянные идентификаторы, которые будут заменены на FilteredObjects во время удаления.

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

Обновление (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 голос
/ 24 декабря 2010

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

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

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

1 голос
/ 12 ноября 2010

Вот как бы я это сделал (я делал что-то подобное раньше, и это работало):

  1. Напишите функцию, которая определяет, будет ли объект замачиваться
  2. Сделатьсписок всех перестраиваемых переменных, основанный на вышеупомянутой функции
  3. Создайте новый словарь (называемый D), в котором будут храниться все неперекачиваемые переменные
  4. Для каждой переменной в D (это работает, только еслиу вас есть очень похожие переменные в d) составьте список строк, где каждая строка является допустимым кодом Python, так что, когда все эти строки выполняются по порядку, вы получите требуемую переменную

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

Надеюсь, это поможет

0 голосов
/ 10 ноября 2010

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

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

Я сделал копию стандартного copy модуля и сделал следующие вещи:

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

Ниже приведен diff:

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

0 голосов
/ 02 ноября 2010

Один из подходов заключается в наследовании от 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, корректно ли это переопределение.

...