2012-02-26 82 views
6

我試圖讓一個對象像一個內置的list一樣行爲,除了它的值在修改後保存。如何實現持久化Python列表?

我想到的實現是將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方法。

我該怎麼做才能簡化這個?

+0

如果你調用列表中的方法之一將引發異常怎麼辦?你還想要保存嗎?你目前的解決方案仍然... – 2012-02-26 01:48:37

回答

4

我喜歡@andrew庫克的回答,但我看不出有任何理由,你爲什麼不能直接派生一個列表。

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 
+0

是的,這是更好的。 – 2012-02-26 01:57:48

+0

你的'_save'看起來像什麼?你怎麼加載對象?我天真的嘗試用'pickle'來做這件事是行不通的。 pickle.dumps(self)''不起作用,而'pickle.dumps(list(self))'做。或者每次'_save'運行()時都會轉換爲列表? – kuzzooroo 2014-07-13 22:56:03

+0

另外,是什麼讓你確信你不需要在你的mutators列表中包含'__delitem__','__delslice__','__iadd__','__imul__','__reversed__','__setitem__','__setslice __' – kuzzooroo 2014-07-14 00:52:38

0

我知道這不是漂亮或聰明,但我只想寫單獨的方法了...

class PersistentList(object): 
    ... 

    def append(self, o): 
     self._autosave() 
     self._list.append(o) 

    ...etc... 
3

這是一種避免必須修飾每個列表方法的方法。它使PersistentList一個context manager,這樣你就可以使用

with PersistentList('key', db) as persistent: 
    do_stuff() 

語法。無可否認,這並不會導致_save方法在每次列表操作後才被調用,只有當您退出with-block時纔會調用該方法。但是我認爲它可以讓你有足夠的控制權來保存你想要保存的內容,尤其是因爲無論你如何離開with-block,包括如果因爲例外而發生這種情況,__exit__方法都會保證執行。

在每個列表操作之後,您可能會收到_save不會被調用的優勢。想象一下,在列表中追加10000次。所以很多個人撥打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] 
+0

如果你願意,你可以通過混合兩種技術來獲得更多的花式,這樣你就可以保持一個「髒」的標誌,表明它需要保存。你甚至可以這樣做,以便'PersistenList .__ del__'會抱怨或嘗試保存(如果系統正在退出它可能會失敗),如果它很髒。 – 2012-02-26 13:38:11

+0

@ChrisMorgan:我喜歡你的想法,但我認爲這將很難正確實施。例如,如果用戶「追加」,然後「流行」,一個天真的實現(通過裝飾每個列表方法)會錯誤地設置「髒」標誌。爲了做得更好,你需要在'__enter__'和每個列表方法測試中保存列表的副本,如果列表是髒的。所有這些比較可能會使表現變慢。由於一般情況下你想保存,也許最好是有點浪費,並且每次都保存。 – unutbu 2012-02-26 14:29:15

+0

我只是把它作爲一個基本的指標,事情已經改變。當然,這些變化可能已經被撤銷,但正如你所說,防止不必要寫入的成本太高了。 – 2012-02-26 14:32:47

0

這裏有一個答案,這是一個很像@ unutbu的,但更普遍。它給你一個你可以調用的函數來同步你的對象到磁盤,它可以與list以外的其他pickle-able類一起工作。

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