如何实现持久的Python`list`?

时间:2012-02-26 01:16:20

标签: python

我正在尝试使对象像内置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)

此实施存在几个问题:

  1. 每次我都必须装饰像append这样的方法 访问,是否有更好的方法来装饰一些方法的多种方法 对象

  2. l += [1,2,3]这样的操作不起作用,因为我没有 实施了 iadd 方法。

  3. 我该怎么做才能简化这个?

4 个答案:

答案 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()