如何实现持久化的Python`list`?
我试图使一个对象像一个内置的如何实现持久化的Python`list`?,python,Python,我试图使一个对象像一个内置的列表,只是它的值一旦修改就可以保存 我提出的实现是将列表包装到持久列表类中。对于每个可能更改列表的方法的访问,包装器都会委托给包装的列表,并在调用后将其保存到键值数据库 代码: 此实现存在几个问题: 每次我都要装饰像append这样的方法 访问后,是否有更好的方法来装饰某些对象的多种方法 反对 像l+=[1,2,3]这样的操作不起作用,因为我没有 实施iadd方法 我能做些什么来简化它呢?我知道它既不漂亮也不聪明,但我只想把各个方法写出来 class Persiste
列表
,只是它的值一旦修改就可以保存
我提出的实现是将列表
包装到持久列表
类中。对于每个可能更改列表的方法的访问,包装器都会委托给包装的列表
,并在调用后将其保存到键值数据库
代码:
此实现存在几个问题:
append
这样的方法
访问后,是否有更好的方法来装饰某些对象的多种方法
反对l+=[1,2,3]
这样的操作不起作用,因为我没有
实施iadd方法我能做些什么来简化它呢?我知道它既不漂亮也不聪明,但我只想把各个方法写出来
class PersistentList(object):
...
def append(self, o):
self._autosave()
self._list.append(o)
...etc...
我喜欢@andrew cooke的答案,但我认为没有理由不能直接从列表中得出答案
class PersistentList(list):
def __init__(self, *args, **kwargs):
for attr in ('append', 'extend', 'insert', 'pop', 'remove', 'reverse', 'sort'):
setattr(self, attr, self._autosave(getattr(self, attr))
list.__init__(self, *args, **kwargs)
def _autosave(self, func):
@wraps(func)
def _func(*args, **kwargs):
ret = func(*args, **kwargs)
self._save()
return ret
return _func
这里有一种避免修饰每个列表方法的方法。它使PersistentList成为一个,因此您可以使用
with PersistentList('key', db) as persistent:
do_stuff()
语法。诚然,这不会导致在每次列表操作后调用\u save
方法,仅当您退出带有block的时。但我认为它在您想要保存时为您提供了足够的控制来保存,特别是因为无论您如何将保留为block
,包括由于异常而发生的情况,\uuuuuuuuuuuuuuuuuuuu
方法都保证执行
您的一个优势可能是,在每次列表操作后都不会调用\u save
。想象一下在列表中添加10000次。对db.set
(数据库?)的这么多单独调用可能相当耗时。我会更好,至少从性能的角度来看,让所有的附件和保存一次
这里有一个很像@unutbu的答案,但更一般。它为您提供了一个可以调用的函数,用于将对象同步到磁盘,并可与列表
之外的其他可pickle类一起使用
with pickle_wrap(os.path.expanduser("~/Desktop/simple_list"), list) as (lst, lst_sync):
lst.append("spam")
lst_sync()
lst.append("ham")
print(str(lst))
# lst is synced one last time by __exit__
下面是使这成为可能的代码:
import contextlib, pickle, os, warnings
def touch_new(filepath):
"Will fail if file already exists, or if relevant directories don't already exist"
# http://stackoverflow.com/a/1348073/2829764
os.close(os.open(filepath, os.O_WRONLY | os.O_CREAT | os.O_EXCL))
@contextlib.contextmanager
def pickle_wrap(filepath, make_new, check_type=True):
"Context manager that loads a file using pickle and then dumps it back out in __exit__"
try:
with open(filepath, "rb") as ifile:
result = pickle.load(ifile)
if check_type:
new_instance = make_new()
if new_instance.__class__ != result.__class__:
# We don't even allow one class to be a subclass of the other
raise TypeError(("Class {} of loaded file does not match class {} of "
+ "value returned by make_new()")
.format(result.__class__, new_instance.__class__))
except IOError:
touch_new(filepath)
result = make_new()
try:
hash(result)
except TypeError:
pass
else:
warnings.warn("You probably don't want to use pickle_wrap on a hashable (and therefore likely immutable) type")
def sync():
print("pickle_wrap syncing")
with open(filepath, "wb") as ofile:
pickle.dump(result, ofile)
yield result, sync
sync()
如果您调用的某个列表方法引发异常怎么办?你还想保存吗?您当前的解决方案仍然可以做到这一点……如果您愿意,您可以通过混合这两种技术来获得更多的乐趣,这样您就可以保留一个“脏”标志,指示需要保存它。您甚至可以这样做,persistenlt.\uu del\uuu
会抱怨或试图保存(如果系统退出,它可能会失败)。@ChrisMorgan:我喜欢您的想法,但我认为很难正确实施。例如,如果用户要追加
,那么弹出
,一个简单的实现(通过修饰每个列表方法)会错误地设置脏
标志。为了做得更好,您需要在\uuuu\enter\uuu
和每个列表方法测试中保存列表的副本(如果列表不干净)。所有这些比较可能会使性能变慢。因为一般来说,你会想存钱,也许最好是有点浪费,每次都存钱。我只把它作为一个基本指标,表明事情已经改变了。当然,更改可能已经撤消,但正如您所说,防止不必要的写入的成本太高了。@ChrisMorgan:啊,我明白了。在这种情况下,我认为我们可以假设,如果用户将与p
一起使用,那么p将会改变。因此,我们可以想当然地认为脏旗会被设置。我们可以在中保存原始列表的副本,然后在中进行比较,看看是否需要调用\u save
。这将避免装饰所有的列表方法。您的\u save
是什么样子的?那么如何将对象加载回?我天真的尝试使用pickle
这样做是行不通的<代码>pickle.dumps(self)
不起作用,而pickle.dumps(list(self))
起作用。还是每次运行\u save
时都转换成一个列表()?还有,是什么让你确信你不必将'\u delitem'、'\u delslice'、'\u iadd'、'\u imul'、'\u反转的'\uu'、'\uu setitem'、'\uu setslice'
包含在你的变体列表中?不知道save看起来像什么。我相信我是基于一个已经被删除的答案。您可能应该包括任何可以修改列表数据的函数,包括您提到的大多数函数(尽管我不认为reversed是一个变种)。我通过执行set(dir(list))-set(dir(tuple))
得到了方法列表。我想创建迭代器需要以某种方式改变列表。OTOH反向
(无下划线)用于元组。我是为了找出原因而创作的。
with pickle_wrap(os.path.expanduser("~/Desktop/simple_list"), list) as (lst, lst_sync):
lst.append("spam")
lst_sync()
lst.append("ham")
print(str(lst))
# lst is synced one last time by __exit__
import contextlib, pickle, os, warnings
def touch_new(filepath):
"Will fail if file already exists, or if relevant directories don't already exist"
# http://stackoverflow.com/a/1348073/2829764
os.close(os.open(filepath, os.O_WRONLY | os.O_CREAT | os.O_EXCL))
@contextlib.contextmanager
def pickle_wrap(filepath, make_new, check_type=True):
"Context manager that loads a file using pickle and then dumps it back out in __exit__"
try:
with open(filepath, "rb") as ifile:
result = pickle.load(ifile)
if check_type:
new_instance = make_new()
if new_instance.__class__ != result.__class__:
# We don't even allow one class to be a subclass of the other
raise TypeError(("Class {} of loaded file does not match class {} of "
+ "value returned by make_new()")
.format(result.__class__, new_instance.__class__))
except IOError:
touch_new(filepath)
result = make_new()
try:
hash(result)
except TypeError:
pass
else:
warnings.warn("You probably don't want to use pickle_wrap on a hashable (and therefore likely immutable) type")
def sync():
print("pickle_wrap syncing")
with open(filepath, "wb") as ofile:
pickle.dump(result, ofile)
yield result, sync
sync()