我有一个昂贵的函数,它接受并返回少量数据(一些整数和浮点数)。我已经有了memoized这个函数,但我想让备忘录持久化。已经有几个线程与此相关,但我不确定某些建议方法的潜在问题,我有一些相当具体的要求:我肯定会同时从多个线程和进程中使用这个函数(都使用multiprocessing和来自不同的python脚本)
我不需要从这个python函数外部对memo进行读写访问
如果我不想在不小心损坏的情况下,我不想在写备忘录的时候把它弄坏,或者手动终止python进程(我不知道这有多现实)
我强烈希望不需要大型外部库的解决方案,因为我在一台运行代码的机器上的硬盘空间非常有限
我对跨平台代码的偏好很弱,但我可能只在Linux上使用它
This thread讨论了shelve模块,它显然不是进程安全的。其中两个答案建议使用fcntl.flock锁定搁置文件。然而,在this thread中的一些回答似乎表明这是一个充满问题的问题-但我并不确切地确定它们是什么。这听起来好像仅限于Unix(尽管显然Windows有一个等价的名称msvcrt.locking),而且锁只是“咨询性的”——也就是说,如果不检查文件是否被锁定,它不会阻止我意外地写入文件。还有其他潜在的问题吗?写入文件的副本并作为最后一步替换主副本,是否会降低损坏的风险?在
看起来dbm module不会比架子更好。我已经快速浏览了sqlite3,但它似乎有点过头了。This thread和{a7}提到了几个第三方库,包括ZODB,但是有很多选择,它们对于这个任务来说都显得过于庞大和复杂。在
有人有什么建议吗?在
更新:kindall在下面提到了IncPy,看起来确实很有趣。不幸的是,我不想回到Python2.6(我实际上在使用3.2),而且它看起来与C库一起使用有点尴尬(我大量使用了numpy和scipy等)。在
kindall的另一个想法很有启发性,但我认为将其应用于多个进程会有点困难——我想用文件锁定或数据库代替队列是最容易的。在
再看一下ZODB,它看起来确实非常适合这个任务,但是我确实想避免使用任何额外的库。我仍然不完全确定简单使用flock的所有问题是什么-我想一个大问题是进程在写入文件时或释放锁之前终止?在
所以,我接受了synthesizerpatel的建议,使用sqlite3。如果有人感兴趣,我决定做一个插入式替换dict,它将其条目作为pickle存储在数据库中(我不必费心在内存中保存任何内容,因为数据库访问和pickle相比,pickle已经足够快了)。我确信有更有效的方法来实现这一点(我不知道我是否仍然存在并发问题),但下面是代码: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]