У меня есть дорогостоящая функция, которая берет и возвращает небольшое количество данных (несколько целых чисел и поплавков). У меня уже есть memoized эта функция, но я хотел бы сделать напоминание постоянным. Есть уже несколько нитей, связанные с этим, но я не уверен о возможных проблемах с некоторыми из предложенных подходов, и у меня есть некоторые довольно специфические требования:Постоянная memoization в Python
- Я определенно использовать функцию из нескольких потоков и процессы одновременно (как с использованием
multiprocessing
и из отдельных сценариев Python) - мне не нужно читать и писать доступ к памятке из вне этой питона функции
- Я не то, что озабоченности по поводу записки коррумпируются в редких случаях (например, потянув вилку или случайно записать в файл без ее блокировки), поскольку это не , что дорогостоящий для восстановления (обычно 10-20 минут), но я бы предпочел, чтобы он не был поврежден из-за исключений или вручную завершил процесс python (я не знаю, насколько это реально)
- Я бы предпочел решения которые не требуют больших внешних библиотек, поскольку у меня на жестком диске имеется ограниченное количество места на жестком диске. Я буду запускать код на
- У меня слабое предпочтение для кросс-платформенного кода, но я, скорее всего, буду использовать этот на Linux
This thread обсуждает модуль shelve
, который, по-видимому, не является безопасным процессом. Два из них предлагают использовать fcntl.flock
для блокировки файла полки. Однако некоторые из ответов в this thread, похоже, предполагают, что это чревато проблемами - но я не совсем уверен, что это такое. Похоже, что это ограничивается Unix (хотя, видимо, Windows имеет эквивалент, называемый msvcrt.locking
), а блокировка - только «консультативная», то есть это не помешает мне случайно записать в файл, не проверяя, что он заблокирован. Существуют ли другие потенциальные проблемы? Написал бы копию файла и заменил бы главную копию как последний шаг, уменьшив риск коррупции?
Не похоже, что dbm module будет лучше, чем полка. Я быстро посмотрел на sqlite3, но для этой цели это кажется немного излишним. This thread и this one упоминают несколько сторонних библиотек, в том числе ZODB, но есть много вариантов, и все они кажутся слишком большими и сложными для этой задачи.
У кого-нибудь есть совет?
ОБНОВЛЕНИЕ: kindall упоминается IncPy ниже, что выглядит очень интересно. К сожалению, я бы не хотел возвращаться к Python 2.6 (я на самом деле использую 3.2), и похоже, что это немного неудобно использовать с библиотеками C (среди прочих, я использую numpy и scipy).
kindall другая идея поучительна, но я думаю, что адаптация этого к нескольким процессам была бы немного сложной - я полагаю, было бы проще заменить очередь на блокировку файла или базу данных.
Глядя на ZODB снова, он отлично подходит для задачи, но я действительно хочу избежать использования каких-либо дополнительных библиотек. Я все еще не совсем уверен, что все проблемы с просто использованием flock
- я думаю, что одна большая проблема заключается в том, что процесс завершается во время записи в файл или до освобождения блокировки?
Итак, я взял совет synhesizerpatel и ушел с sqlite3. Если кому-то это интересно, я решил сделать замену на dict
, которая хранит свои записи в виде соленья в базе данных (я не хочу хранить в памяти, поскольку доступ к базе данных и травление достаточно быстро по сравнению со всем остальным, что я делать). Я уверен, что есть более эффективные способы сделать это (и я понятия не имею, могу ли я все еще есть проблемы параллелизма), но вот код:
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]
Если вы имеете дело с Python 2.6.3 и не находитесь в Windows, вы можете проверить [IncPy] (http://www.stanford.edu/~pgbovine/incpy.html), который будет автоматически и постоянно сохраняйте свою * всю программу * везде, где это безопасно. – kindall