我正在尝试使对象像内置list
一样,除了在修改后保存其值。
我提出的实现是将list
包装在PersistentList
类中。对于可能更改列表的方法的每次访问,包装器委托给包装的list
,并在调用它之后将其保存到键值数据库。
代码:
class PersistentList(object):
def __init__(self, key):
self.key = key
self._list = db.get(key, [])
def __getattr__(self, name):
attr = getattr(self._list, name)
if attr:
if attr in ('append', 'extend', 'insert', 'pop',
'remove', 'reverse', 'sort'):
attr = self._autosave(attr)
return attr
raise AttributeError
def _autosave(self, func):
@wraps(func)
def _(*args, **kwargs):
ret = func(*args, **kwargs)
self._save()
return ret
return _
def _save(self):
db.set(self.key, self._list)
此实施存在几个问题:
每次我都必须装饰像append
这样的方法
访问,是否有更好的方法来装饰一些方法的多种方法
对象
像l += [1,2,3]
这样的操作不起作用,因为我没有
实施了 iadd 方法。
我该怎么做才能简化这个?
答案 0 :(得分:6)
我喜欢@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
答案 1 :(得分:3)
这是一种避免必须装饰每个列表方法的方法。它使PersistentList成为context manager,因此您可以使用
with PersistentList('key', db) as persistent:
do_stuff()
语法。不可否认,只有当您退出_save
时,才会导致在每个列表操作后调用with-block
方法。但是我认为它可以让你有足够的控制权来保存你想要保存的时间,特别是因为无论你如何离开__exit__
都保证执行with-block
方法,包括是否因为异常而发生
在每个列表操作之后,_save
不会被调用,这可能是一个优点。想象一下,将该列表附加10,000次。如此多的单独调用db.set
(一个数据库?)可能非常耗时。至少从性能的角度来看,我会更好地完成所有追加和保存一次。
class PersistentList(list):
def __init__(self, key, db):
self.key = key
self.extend(db.get(key, []))
def _save(self):
# db.set(self.key, self)
print('saving {x}'.format(x = self))
def __enter__(self):
return self
def __exit__(self,ext_type,exc_value,traceback):
self._save()
db = {}
p = PersistentList('key', db)
with p:
p.append(1)
p.append(2)
with p:
p.pop()
p += [1,2,3]
# saving [1, 2]
# saving [1, 1, 2, 3]
答案 2 :(得分:0)
我知道它不漂亮或聪明,但我只想写出各个方法......
class PersistentList(object):
...
def append(self, o):
self._autosave()
self._list.append(o)
...etc...
答案 3 :(得分:0)
这是一个很像@ unutbu的答案,但更通用。它为您提供了一个函数,您可以调用它来将对象同步到磁盘,它可以与除list
之外的其他可修改的类一起使用。
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()