Настойчивость объекта Python
Я ищу совет о методах реализации сохранения объектов в Python. Чтобы быть более точным, я хочу уметь связывать объект Python с файлом таким образом, что любой процесс Python, который открывает представление этого файла, имеет одинаковую информацию, любой процесс может изменить его объект, и изменения будут распространяться на другие процессы, и даже если все процессы, "сохраняющие" объект, будут закрыты, файл останется и может быть снова открыт другим процессом.
Я нашел для себя три основных кандидата в моем распределении Python - anydbm, pickle и shelve (dbm оказался идеальным, но это Unix-only, и я нахожусь в Windows). Однако все они имеют недостатки:
- anydbm может обрабатывать только словарь строковых значений (я хочу хранить список словарей, все из которых имеют строковые ключи и строковые значения, хотя в идеале я бы искал модуль без ограничений типа)
- shelve требует, чтобы файл был повторно открыт до того, как изменения распространились - например, если два процесса A и B загружают один и тот же файл (содержащий полки пустой список), а A добавляет элемент в список и вызывает sync(), B все равно будет видеть, что список пуст, пока он не перезагрузит файл.
- pickle (модуль, который я сейчас использую для моей тестовой реализации) имеет такое же "требование перезагрузки", как и отложенное, а также не перезаписывает предыдущие данные - если процесс A выгружает пятнадцать пустых строк в файл, а затем строка ' hello ', процесс B должен будет загрузить файл шестнадцать раз, чтобы получить строку "hello". В настоящее время я сталкиваюсь с этой проблемой, предшествуя любой операции записи с повторными чтениями до конца файла ( "очистка слайта до его записи" ) и делая каждую операцию чтения повторяющейся до конца файла, но я чувствую, что должно быть лучший способ.
Мой идеальный модуль будет вести себя следующим образом (с "A → > ", представляющим код, выполняемый процессом A, и код "B → > ", выполняемый процессом B):
A>>> import imaginary_perfect_module as mod
B>>> import imaginary_perfect_module as mod
A>>> d = mod.load('a_file')
B>>> d = mod.load('a_file')
A>>> d
{}
B>>> d
{}
A>>> d[1] = 'this string is one'
A>>> d['ones'] = 1 #anydbm would sulk here
A>>> d['ones'] = 11
A>>> d['a dict'] = {'this dictionary' : 'is arbitrary', 42 : 'the answer'}
B>>> d['ones'] #shelve would raise a KeyError here, unless A had called d.sync() and B had reloaded d
11 #pickle (with different syntax) would have returned 1 here, and then 11 on next call
(etc. for B)
Я мог бы добиться такого поведения, создав свой собственный модуль, который использует pickle, и отредактировал поведение дампа и нагрузки, чтобы они использовали повторяющиеся чтения, о которых я упоминал выше, - но мне трудно поверить, что эта проблема никогда не возникала, и были исправлены более талантливыми программистами. Более того, эти повторяющиеся чтения кажутся мне неэффективными (хотя я должен признать, что мое знание сложности операции ограничено, и возможно, что эти повторные чтения продолжаются "за кулисами" в других, по-видимому, более плавных модулях, таких как полка). Поэтому я пришел к выводу, что мне не хватает кода, который бы разрешил проблему для меня. Я был бы признателен, если бы кто-нибудь мог указать мне в правильном направлении или дать рекомендации по реализации.
Ответы
Ответ 1
Используйте ZODB
(База данных объектов Zope). При поддержке ZEO он отвечает вашим требованиям:
-
Прозрачная настойчивость для объектов Python
ZODB использует соленые огурцы, поэтому все, что можно развести, можно хранить в хранилище объектов ZODB.
-
Полная поддержка транзакций с поддержкой ACID (включая точки сохранения)
Это означает, что изменения одного процесса распространяются на все другие процессы, когда они хороши и готовы, и каждый процесс имеет последовательное представление данных во всей транзакции.
ZODB существует уже более десяти лет, поэтому вы правы в предположении, что эта проблема уже решена раньше.: -)
ZODB позволяет подключать хранилища; наиболее распространенным форматом является FileStorage, который хранит все в одном Data.fs с дополнительным хранилищем памяти для больших объектов.
Некоторые хранилища ZODB являются обертками вокруг других, чтобы добавить функциональность; Например, DemoStorage сохраняет изменения в памяти, чтобы облегчить модульное тестирование и демонстрационные настройки (перезапустить, и вы снова очистите шифер). BeforeStorage дает вам окно во времени, возвращая данные только от транзакций до заданного момента времени. Последний способствовал восстановлению утраченных данных для меня.
ZEO - такой плагин, который представляет архитектуру клиент-сервер. Использование ZEO позволяет одновременно получать доступ к определенному хранилищу из нескольких процессов; вам не понадобится этот слой, если вам нужен только многопоточный доступ только из одного процесса.
То же самое можно было бы достичь с помощью RelStorage, в котором хранятся данные ZODB в реляционной базе данных, такой как PostgreSQL, MySQL или Oracle.
Ответ 2
Для новичков вы можете переносить свои полковые базы данных в базы данных ZODB следующим образом:
#!/usr/bin/env python
import shelve
import ZODB, ZODB.FileStorage
import transaction
from optparse import OptionParser
import os
import sys
import re
reload(sys)
sys.setdefaultencoding("utf-8")
parser = OptionParser()
parser.add_option("-o", "--output", dest = "out_file", default = False, help ="original shelve database filename")
parser.add_option("-i", "--input", dest = "in_file", default = False, help ="new zodb database filename")
parser.set_defaults()
options, args = parser.parse_args()
if options.in_file == False or options.out_file == False :
print "Need input and output database filenames"
exit(1)
db = shelve.open(options.in_file, writeback=True)
zstorage = ZODB.FileStorage.FileStorage(options.out_file)
zdb = ZODB.DB(zstorage)
zconnection = zdb.open()
newdb = zconnection.root()
for key, value in db.iteritems() :
print "Copying key: " + str(key)
newdb[key] = value
transaction.commit()