У меня есть дорогая функция, которая принимает и возвращает небольшое количество данных (несколько целых чисел и число с плавающей запятой). Я уже запомнил эту функцию, но я хотел бы сделать памятку постоянной. Уже есть несколько потоков, связанных с этим, но я не уверен относительно потенциальных проблем с некоторыми из предложенных подходов, и у меня есть некоторые довольно специфические требования:
- Я обязательно буду использовать функцию из нескольких потоков и процессов одновременно (как с использованием
multiprocessing
, так и из отдельных скриптов Python)
- Мне не нужен доступ для чтения или записи к заметке извне этой функции Python
- Меня не беспокоит то, что памятка может быть повреждена в редких случаях (например, потянув за вилку или случайно записать в файл, не блокируя его), поскольку это не так дорого для восстановления (обычно 10- 20 минут), но я бы предпочел, чтобы он не был поврежден из-за исключений или вручную завершил процесс Python (я не знаю, насколько это реалистично)
- Я бы настоятельно предпочел решения, которые не требуют больших внешних библиотек, так как у меня очень ограниченный объем дискового пространства на одной машине, я буду запускать код на
- У меня слабое предпочтение кросс-платформенного кода, но я, скорее всего, буду использовать его только в Linux
В этой теме обсуждается модуль shelve
, который, очевидно, не безопасен для процесса. В двух ответах предлагается использовать fcntl.flock
для блокировки файла полки. Однако некоторые ответы в этой теме , похоже, предполагают, что это чревато проблемами - но я не совсем уверен, что они есть. Звучит так, как будто это ограничено Unix (хотя, по-видимому, Windows имеет эквивалент msvcrt.locking
), и блокировка носит только «рекомендательный характер», то есть она не остановит меня от случайной записи в файл без проверки его блокировки. , Есть ли другие потенциальные проблемы? Будет ли запись в копию файла и замена главной копии в качестве последнего шага уменьшением риска повреждения?
Не похоже, что модуль dbm будет работать лучше, чем полки. Я быстро взглянул на sqlite3 , но это кажется немного излишним для этой цели. В этой теме и в этой упоминается несколько сторонних библиотек, включая ZODB , но существует множество вариантов, и все они кажутся слишком большими и сложными для этого задача.
У кого-нибудь есть совет?
ОБНОВЛЕНИЕ : любезно упомянул IncPy ниже, что выглядит очень интересно. К сожалению, я бы не хотел возвращаться к Python 2.6 (на самом деле я использую 3.2), и, похоже, его немного неудобно использовать с библиотеками C (среди прочего, я часто использую numpy и scipy).
Другая идея kindall поучительна, но я думаю, что адаптировать ее к нескольким процессам будет немного сложно - я полагаю, было бы проще заменить очередь блокировкой файлов или базой данных.
Глядя на ZODB еще раз, он выглядит идеально для этой задачи, но я действительно хочу избегать использования каких-либо дополнительных библиотек. Я до сих пор не совсем уверен, что все проблемы с простым использованием flock
- я представляю одну большую проблему, если процесс завершается во время записи в файл или до снятия блокировки?
Итак, я воспользовался советом synthesizerpatel и ушел с 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]