我有一个昂贵的函数,它接受并返回少量数据(一些整数和浮点数)。我已经memoized http://en.wikipedia.org/wiki/Memoization这个功能,但我想让备忘录持久化。已经有几个与此相关的线程,但我不确定某些建议方法的潜在问题,并且我有一些相当具体的要求:
- 我肯定会同时使用多个线程和进程的函数(都使用
multiprocessing
以及来自单独的 python 脚本)
- 我不需要从这个 python 函数外部读取或写入备忘录
- 我并不担心备忘录在极少数情况下会被损坏(例如拔掉插头或意外写入文件而不锁定它),因为它不是that重建成本高昂(通常需要 10-20 分钟),但我希望它不会因为异常而损坏,或者手动终止 python 进程(我不知道这有多现实)
- 我强烈喜欢不需要大型外部库的解决方案,因为我将在一台机器上运行代码,其硬盘空间非常有限
- 我对跨平台代码的偏好较弱,但我可能只会在 Linux 上使用它
这个线程 https://stackoverflow.com/questions/486490/python-shelve-module-question讨论了shelve
模块,这显然不是进程安全的。其中两个答案建议使用fcntl.flock
锁定搁置文件。中的一些回复这个线程 https://stackoverflow.com/questions/489861/locking-a-file-in-python然而,似乎表明这充满了问题 - 但我不太确定它们是什么。听起来好像这仅限于 Unix(尽管显然 Windows 有一个等效的称为msvcrt.locking
),并且锁定只是“建议” - 即,它不会阻止我在不检查文件是否已锁定的情况下意外写入文件。还有其他潜在的问题吗?写入文件的副本并作为最后一步替换主副本是否可以降低损坏的风险?
看起来不像数据库模块 http://docs.python.org/py3k/library/dbm.html会比搁置更好。我快速浏览了一下sqlite3 http://docs.python.org/library/sqlite3.html,但为了这个目的似乎有点矫枉过正。这个线程 https://stackoverflow.com/questions/1235594/comparing-persistent-storage-solutions-in-python and this one https://stackoverflow.com/questions/8428103/is-there-an-established-memoize-on-disk-decorator-for-python提到几个第三方库,包括ZODB http://www.zodb.org/index.html,但是有很多选择,对于这个任务来说,它们都显得过于庞大和复杂。
有人有建议吗?
UPDATE:下面提到了 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]