Ho una funzione costosa che accetta e restituisce una piccola quantità di dati (alcuni interi e float). Ho già memoized questa funzione, ma mi piacerebbe rendere il memo persistente. Ci sono già un paio di discussioni relative a questo, ma sono sicuro di potenziali problemi con alcuni degli approcci proposti, e ho alcuni requisiti piuttosto specifici:Memoizzazione persistente in Python
- Io sicuramente utilizzare la funzione da più thread e processi contemporaneamente (sia utilizzando
multiprocessing
e da script python separati) - non avrò bisogno di leggere o scrivere l'accesso alla nota al di fuori di questa funzione pitone
- non sono così preoccupati per l'appunto essere corrotto in rare occasioni (come tirare la spina o scrivere accidentalmente sul file senza bloccarlo) in quanto non è che costoso da ricostruire (in genere 10-20 minuti) ma preferirei se non fosse danneggiato a causa di eccezioni, o terminando manualmente un processo python (non so quanto sia realistico)
- Preferisco fortemente soluzioni che non richiedono grandi librerie esterne in quanto ho una quantità limitata di spazio su disco su un unico computer eseguirò il codice su
- Ho una debole preferenza per il codice multipiattaforma, ma probabilmente userò solo questo su Linux
This thread discute il modulo shelve
, che apparentemente non è sicuro per il processo. Due delle risposte suggeriscono di utilizzare fcntl.flock
per bloccare il file shelve. Alcune delle risposte in this thread, tuttavia, sembrano suggerire che questo è pieno di problemi - ma non sono esattamente sicuro di cosa siano. Sembra che questo sia limitato a Unix (anche se apparentemente Windows ha un equivalente chiamato msvcrt.locking
), e il blocco è solo "advisory" - cioè, non mi impedirà di scrivere accidentalmente sul file senza averlo controllato. Ci sono altri potenziali problemi? Scrivere in una copia del file e sostituire la copia master come passaggio finale, ridurre il rischio di corruzione?
Non sembra che lo dbm module farà meglio di accantonare. Ho dato uno sguardo veloce a sqlite3, ma sembra un po 'eccessivo per questo scopo. This thread e this one menzionano diverse librerie di terze parti, tra cui ZODB, ma ci sono molte scelte e sembrano tutte eccessivamente grandi e complicate per questo compito.
Qualcuno ha qualche consiglio?
UPDATE: kindall citato IncPy di seguito, che sembra molto interessante. Sfortunatamente, non vorrei tornare a Python 2.6 (attualmente sto usando 3.2), e sembra che sia un po 'scomodo da usare con le librerie C (io uso pesantemente di numpy e scipy, tra gli altri).
L'altra idea di kindall è istruttiva, ma ritengo che adattarla a più processi sarebbe un po 'difficile - suppongo che sarebbe più semplice sostituire la coda con il blocco dei file o un database.
Guardando di nuovo a ZODB, sembra perfetto per l'attività, ma io davvero voglio evitare di usare altre librerie aggiuntive. Non sono ancora del tutto sicuro di quali siano i problemi con il semplice utilizzo di flock
- Immagino che un grosso problema sia se un processo viene terminato mentre si scrive sul file o prima di rilasciare il blocco?
Quindi, ho preso il consiglio di synthesizerpatel e sono andato con sqlite3. Se qualcuno è interessato, ho deciso di fare un rimpiazzo in sostituzione di dict
che memorizza le sue voci come sottaceti in un database (non mi preoccupo di conservarle in memoria come accesso al database e il pickling è abbastanza veloce rispetto a qualsiasi altra cosa io sia facendo). Sono sicuro che ci sono modi più efficaci per farlo (e non ho idea se potrei ancora avere problemi di concorrenza), ma qui è il codice:
from collections import MutableMapping
import sqlite3
import pickle
class PersistentDict(MutableMapping):
def __init__(self, dbpath, iterable=None, **kwargs):
self.dbpath = dbpath
with self.get_connection() as connection:
cursor = connection.cursor()
cursor.execute(
'create table if not exists memo '
'(key blob primary key not null, value blob not null)'
)
if iterable is not None:
self.update(iterable)
self.update(kwargs)
def encode(self, obj):
return pickle.dumps(obj)
def decode(self, blob):
return pickle.loads(blob)
def get_connection(self):
return sqlite3.connect(self.dbpath)
def __getitem__(self, key):
key = self.encode(key)
with self.get_connection() as connection:
cursor = connection.cursor()
cursor.execute(
'select value from memo where key=?',
(key,)
)
value = cursor.fetchone()
if value is None:
raise KeyError(key)
return self.decode(value[0])
def __setitem__(self, key, value):
key = self.encode(key)
value = self.encode(value)
with self.get_connection() as connection:
cursor = connection.cursor()
cursor.execute(
'insert or replace into memo values (?, ?)',
(key, value)
)
def __delitem__(self, key):
key = self.encode(key)
with self.get_connection() as connection:
cursor = connection.cursor()
cursor.execute(
'select count(*) from memo where key=?',
(key,)
)
if cursor.fetchone()[0] == 0:
raise KeyError(key)
cursor.execute(
'delete from memo where key=?',
(key,)
)
def __iter__(self):
with self.get_connection() as connection:
cursor = connection.cursor()
cursor.execute(
'select key from memo'
)
records = cursor.fetchall()
for r in records:
yield self.decode(r[0])
def __len__(self):
with self.get_connection() as connection:
cursor = connection.cursor()
cursor.execute(
'select count(*) from memo'
)
return cursor.fetchone()[0]
Se è possibile gestire Python 2.6.3 e non è su Windows, si potrebbe voler controllare [IncPy] (http://www.stanford.edu/~pgbovine/incpy.html) che verrà automaticamente e memorizza costantemente il tuo * intero programma * ovunque sia sicuro farlo. – kindall