#python #json #persistence #yaml #pickle
#python #json #постоянство #yaml #pickle
Вопрос:
Я использую модуль pickle от Python для реализации тонкого слоя сохранения на основе файлов. Уровень сохранения (часть более крупной библиотеки) в значительной степени зависит от функции persistent_id от pickle для сохранения объектов указанных классов в виде отдельных файлов.
Единственная проблема с этим подходом заключается в том, что файлы pickle недоступны для редактирования человеком, и я бы предпочел, чтобы объекты сохранялись в формате, удобочитаемом и редактируемом с помощью текстового редактора (например, YAML или JSON).
Знаете ли вы какую-либо библиотеку, которая использует формат, доступный для редактирования человеком, и предлагает функции, аналогичные pickle persistent_id
? В качестве альтернативы, есть ли у вас предложения по их реализации поверх библиотеки сериализации на основе YAML или JSON, без перезаписи большого подмножества pickle?
Комментарии:
1. Трудно вносить предложения о том, как реализовать систему сохранения без какого-либо описания ее назначения или требований.
2. @taleinat что ж, вопрос имеет более узкую область применения, чем реализация системы сохранения: есть ли какая-либо библиотека, которая предлагает функцию, аналогичную механизму pickle
persistent_id
, но использует формат, доступный для редактирования человеком? (Но вы правы, последняя часть может вызвать некоторую путаницу — я попытаюсь ее перефразировать)
Ответ №1:
Я сам еще не пробовал это, но я думаю, вы сможете сделать это элегантно с помощью PyYAML, используя то, что они называют «представителями» и «распознавателями».
Редактировать
После обширного обмена комментариями с постером, вот метод для достижения требуемого поведения с PyYAML.
Важное примечание: если Persistable
экземпляр имеет другой такой экземпляр в качестве атрибута или каким-то образом содержится внутри одного из его атрибутов, то содержащийся Persistable
экземпляр не будет сохранен в еще один отдельный файл, скорее он будет сохранен встроенным в тот же файл, что и родительский Persistable
экземпляр. Насколько я понимаю, это ограничение также существовало в системе, основанной на pickle, и может быть приемлемым для его / ее вариантов использования. Я не нашел элегантного решения для этого, которое не включало бы взлом yaml.representer.BaseRepresenter
.
import yaml
from functools import partial
class Persistable(object):
# simulate a unique id
_unique = 0
def __init__(self, *args, **kw):
Persistable._unique = 1
self.persistent_id = ("%s.%d" %
(self.__class__.__name__, Persistable._unique))
def persistable_representer(dumper, data):
id = data.persistent_id
print "Writing to file: %s" % id
outfile = open(id, 'w')
outfile.write(yaml.dump(data))
outfile.close()
return dumper.represent_scalar(u'!xref', u'%s' % id)
class PersistingDumper(yaml.Dumper):
pass
PersistingDumper.add_representer(Persistable, persistable_representer)
my_yaml_dump = partial(yaml.dump, Dumper=PersistingDumper)
def persistable_constructor(loader, node):
xref = loader.construct_scalar(node)
print "Reading from file: %s" % id
infile = open(xref, 'r')
value = yaml.load(infile.read())
infile.close()
return value
yaml.add_constructor(u'!xref', persistable_constructor)
# example use, also serves as a test
class Foo(Persistable):
def __init__(self):
self.one = 1
Persistable.__init__(self)
class Bar(Persistable):
def __init__(self, foo):
self.foo = foo
Persistable.__init__(self)
foo = Foo()
bar = Bar(foo)
print "=== foo ==="
dumped_foo = my_yaml_dump(foo)
print dumped_foo
print yaml.load(dumped_foo)
print yaml.load(dumped_foo).one
print "=== bar ==="
dumped_bar = my_yaml_dump(bar)
print dumped_bar
print yaml.load(dumped_bar)
print yaml.load(dumped_bar).foo
print yaml.load(dumped_bar).foo.one
baz = Bar(Persistable())
print "=== baz ==="
dumped_baz = my_yaml_dump(baz)
print dumped_baz
print yaml.load(dumped_baz)
Отныне используйте my_yaml_dump
вместо yaml.dump
, когда вы хотите сохранить экземпляры Persistable
класса в отдельные файлы. Но не используйте его внутри persistable_representer
и persistable_constructor
! Никакой специальной функции загрузки не требуется, просто используйте yaml.load
.
Фух, это потребовало некоторой работы… Надеюсь, это поможет!
Комментарии:
1. Большое спасибо за предложение! Однако я не могу заставить это работать (по крайней мере, нелегко): gist.github.com/1348843 Основные проблемы: (1) PyYAML решает, какой представитель использовать, основываясь только на основном классе объекта, поэтому добавление атрибута
persistent_id
или создание подклассов объектов указанногоPersistent
класса не работает; (2) Поэтому представитель функция не может решить, использовать ли представление «cross-ref» или простое YAML: для этого потребуется указать, когда представитель вызывается как часть сброса другого объекта, или «изначально» — в противном случае мы получаем бесконечную рекурсию.2. Ваш код вводит бесконечную рекурсию, потому что вы вызываете
yaml.dump(data)
внутри представителя, и вы сказалиyaml.dump
использовать этот представитель для сброса этих данных! Попробуйте использовать пример представителя / конструктора на странице документации PyYAML, на которую я ссылался. В частности, вы должны использоватьdumper.represent_scalar
вместоyaml.dump
внутри вашего представителя иloader.construct_scalar
вместоyaml.load
в вашем конструкторе.3. Да, я знаю, почему он входит в бесконечную рекурсию. Дело в том, что я не могу использовать функции
represent_scalar
/construct_scalar
, потому что «постоянные» объекты не являются простыми скалярами, это объекты Python, из которых я должен получить «обычное» представление YAML. Это то, чтоpersistent_id
делает pickle: он генерирует обычное маринованное представление, но сохраняет его в другой файл.4. Я вижу, вы хотите снова получить
yaml.dump
объект, но без специальной обработки, и сохранить его в файл, правильно? Если да, то не будет ли делать то же самое сpickle
той же проблемой? Как вы преодолели это в своем решении на основе pickle?5. Да, правильно. В решении на основе pickle я не делал никакой особой магии; это особенность pickle, описанная здесь .