Tengo una función costosa que toma y devuelve una pequeña cantidad de datos (algunos enteros y flotantes). Ya tengo memoized esta función, pero me gustaría que la nota sea persistente. Ya hay un par de hilos relacionados con este, pero estoy seguro acerca de los posibles problemas con algunos de los enfoques sugeridos, y tengo algunos requisitos bastante específicos:Memorización persistente en Python
- Definitivamente voy a utilizar la función de múltiples hilos y procesos simultáneamente (tanto utilizando
multiprocessing
y de scripts python separadas) - no necesitaré de lectura o escritura a la nota desde fuera esta función pitón
- no estoy tan preocupado por el memorándum se corrompan en raras ocasiones (como tirar el enchufe o accidentalmente escribiendo en el archivo sin bloquearlo) ya que no es que costoso de reconstruir (generalmente 10-20 minutos) pero preferiría que no se corrompiera debido a excepciones, o terminar manualmente un proceso de python (no sé qué tan realista es)
- Preferiría las soluciones que no requieren grandes librerías externas ya que tengo una cantidad de espacio en disco rígidamente limitada en una máquina. Ejecutaré el código en
- Tengo una preferencia débil por el código multiplataforma, pero probablemente solo use esto. en Linux
This thread discute el módulo shelve
, que al parecer no es un proceso seguro. Dos de las respuestas sugieren usar fcntl.flock
para bloquear el archivo de archivado. Algunas de las respuestas en this thread, sin embargo, parecen sugerir que esto está plagado de problemas, pero no estoy exactamente seguro de lo que son. Suena como si esto estuviera limitado a Unix (aunque aparentemente Windows tiene un equivalente llamado msvcrt.locking
), y el bloqueo es solo 'de advertencia', es decir, no me impedirá escribir accidentalmente en el archivo sin verificar que esté bloqueado. ¿Hay algún otro problema potencial? ¿Escribir en una copia del archivo y reemplazar la copia maestra como paso final reduce el riesgo de corrupción?
No parece que dbm module sea mejor que dejar de lado. He echado un vistazo rápido al sqlite3, pero parece un poco exagerado para este propósito. This thread y this one mencionan varias bibliotecas de terceros, incluido ZODB, pero hay muchas opciones, y todas parecen demasiado grandes y complicadas para esta tarea.
¿Alguien tiene algún consejo?
ACTUALIZACIÓN: kindall mencionó IncPy a continuación, que se ve muy interesante. Desafortunadamente, no me gustaría volver a Python 2.6 (en realidad estoy usando 3.2), y parece que es un poco incómodo de usar con las bibliotecas C (hago un uso intensivo de numpy y skipy, entre otros).
La otra idea de kindall es instructiva, pero creo que adaptar esto a múltiples procesos sería un poco difícil, supongo que sería más fácil reemplazar la cola con bloqueo de archivos o una base de datos.
Al volver a mirar ZODB, parece perfecto para la tarea, pero realmente quiero evitar el uso de bibliotecas adicionales. Todavía no estoy del todo seguro de cuáles son los problemas con simplemente usar flock
- Me imagino que un gran problema es si un proceso finaliza al escribir en el archivo o antes de liberar el bloqueo.
Por lo tanto, he tomado el consejo de synthesizerpatel y me he ido con sqlite3. Si alguien está interesado, decidí hacer un reemplazo directo para dict
que almacena sus entradas como pepinillos en una base de datos (no me molesto en guardar ninguno en la memoria ya que el acceso a la base de datos y el encurtido son lo suficientemente rápidos en comparación con todo lo demás) obra). Estoy seguro de que hay formas más eficientes de hacer esto (y no tengo ni idea de si todavía podría tener problemas de concurrencia), pero aquí está el código:
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]
Si puede ocuparse de Python 2.6.3 y no está en Windows, es posible que desee comprobar [IncPy] (http://www.stanford.edu/~pgbovine/incpy.html) que automáticamente y memorice persistentemente su * programa completo * donde sea seguro hacerlo. – kindall