Настойчивость объекта 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()