Python: маринован дикт с некоторыми недоопущенными предметами
-
28-09-2019 - |
Вопрос
У меня есть объект 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
Вот как я бы сделал это (я сделал что-то подобное раньше, и это сработало):
- Напишите функцию, которая определяет, является ли объект оглядываемым
- Сделайте список всех извилистых переменных на основе вышеуказанной функции
- Сделайте новый словарь (называемый D), который хранит все непоколебимые переменные
- Для каждой переменной в 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
модуль, и сделал следующие вещи:
- Создать новый тип с именем
LostObject
представлять объект, который будет потерян в мариновании. - изменять
_deepcopy_atomic
Чтобы убедитьсяx
подбирается. Если это не, вернуть экземплярLostObject
- Объекты могут определять методы
__reduce__
и / или__reduce_ex__
Предоставить подсказку о том, и как его раскрутить. Мы уверены, что эти методы не будут бросать исключение, чтобы обеспечить намек на то, что оно не может быть замаривается. - Чтобы не сделать ненужную копию большого объекта (ля Фактическая дипломная копирование), мы рекурсивно проверяем, является ли объект подборщиком, только сделать только неопытные детали. Например, для кортежа списка подборбару и а не распутный объект, мы сделаем копию кортежа - просто контейнера - но не его список пользователей.
Ниже приведен различие:
[~/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) не новая копия большого объекта создается с тех пор, как она подбирается.