2012-02-26 3 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) 

이 구현 몇 가지 문제가 있습니다 : 나는 append처럼 그들이 액세스 때마다 방법을 장식 할

  1. 는 여러 장식하는 더 나은 방법이 일부 개체의 메서드? 내가하지 가 IADD 방법을 구현해야하기 때문에 l += [1,2,3] 같은

  2. 작업은 작동하지 않습니다.

이것을 단순화하려면 어떻게해야합니까?

+0

당신이 전화 목록의 방법 중 하나는 예외가 발생하는 경우? 아직도 저축을 원하니? 당신은 현재의 해결책이 여전히 그것을합니다 ... –

답변

4

나는 앤드류 쿡의 답변을 좋아하지만 난 당신이 직접 도출 할 수없는 이유 이유를 볼 수 없습니다 목록.

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

네, 그게 더 낫습니다. –

+0

'_save'는 어떻게 생겼습니까? 그리고 어떻게 객체를 다시로드합니까? '피클'을 사용하여 나의 순진한 시도는 작동하지 않습니다. 'pickle.dumps (self)'가하는 동안'pickle.dumps (self)'는 작동하지 않습니다. 아니면'_save' runs() 때마다리스트로 변환하겠습니까? – kuzzooroo

+0

또한 무엇이 mutator 목록에''__delitem__ ','__delslice__ ','__iadd__ ','__imul__ ','__reversed__ ','__setitem__ ','__setslice __ ''를 포함 할 필요가 없다고 확신하게 만들었습니까? – kuzzooroo

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

구문을 사용할 수 있습니다. 물론, with-block을 종료 할 때만 각 목록 작업 후에 _save 메서드가 호출되지 않습니다. 하지만, 특히, __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] 
+0

원할 경우, 두 기술을 혼합하여 더욱 멋지게 만들 수 있으므로 저장해야한다는 "더티"플래그가 유지됩니다. 'PersistenList .__ del__'이 더러워진다면 불평하거나 저장하려고 시도 할 수도 있습니다 (시스템을 종료 할 경우 실패 할 수도 있습니다). –

+0

@ChrisMorgan : 당신의 아이디어가 마음에 들지만 올바르게 구현하는 것이 어려울 것이라고 생각합니다. 예를 들어, 사용자가'pop '을 덧붙이면 순진한 구현 (각리스트 메소드를 꾸미는 것)은'dirty' 플래그를 잘못 설정합니다. 더 잘하려면 목록의 복사본을'__enter__'에 저장하고 목록이 더러운 지 각 목록 메서드 테스트에 저장해야합니다. 이러한 모든 비교는 성능을 저하시킬 수 있습니다. 일반적으로 저장하기를 원할 것이기 때문에 낭비 적이면서 매번 저장하는 것이 좋습니다. – unutbu

+0

나는 물건이 변경되었다는 것을 단지 기본 지시자로 넣었습니다. 물론 변경 사항이 취소되었을 수도 있지만, 말했듯이 불필요한 쓰기 방지 비용은 너무 높을 것입니다. –

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() 
관련 문제