2012-02-26 29 views
6

Tôi đang cố gắng tạo một đối tượng hoạt động như được xây dựng trong list, ngoại trừ giá trị của nó được lưu khi đã sửa đổi.Làm thế nào để triển khai một danh sách `Python` liên tục?

Triển khai tôi đưa ra là gói list trong lớp PersistentList. Đối với mọi truy cập vào phương thức có thể thay đổi danh sách, trình bao bọc ủy quyền cho gói list và lưu nó vào cơ sở dữ liệu khóa-giá trị sau khi nó được gọi.

Code:

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) 

Có một số vấn đề với việc thực hiện này:

  1. Tôi có để trang trí các phương pháp như append mỗi khi họ truy cập, là có một cách tốt hơn để trang trí nhiều phương pháp của một số đối tượng ?

  2. Thao tác như l += [1,2,3] không hoạt động vì tôi chưa triển khai phương thức iadd.

Tôi có thể làm gì để đơn giản hóa điều này?

+0

gì nếu một trong các phương pháp danh sách bạn gọi đặt ra một ngoại lệ? Bạn vẫn muốn làm việc lưu? Giải pháp hiện tại của bạn vẫn làm được ... –

Trả lời

4

Tôi thích câu trả lời Cooke @ Andrew, nhưng tôi thấy không có lý do tại sao bạn không thể lấy trực tiếp từ một danh sách.

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

vâng tốt hơn. –

+0

Hình dạng '_save' của bạn trông như thế nào? Và làm thế nào để bạn tải các đối tượng trở lại? Nỗ lực ngây thơ của tôi để làm như vậy bằng cách sử dụng 'pickle' không hoạt động. 'pickle.dumps (self)' không hoạt động trong khi 'pickle.dumps (danh sách (self))'. Hay bạn chỉ cần chuyển đổi thành một danh sách mỗi khi '_save' chạy()? – kuzzooroo

+0

Ngoài ra, điều gì khiến bạn tự tin rằng bạn không phải bao gồm ''__delitem__', '__delslice__', '__iadd__', '__imul__', '__reversed__', '__setitem__', '__setslice __'' trong danh sách các trình tắt của bạn? – kuzzooroo

0

Tôi biết nó không đẹp hay thông minh, nhưng tôi sẽ chỉ cần viết các phương pháp cá nhân ra ...

class PersistentList(object): 
    ... 

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

    ...etc... 
3

Đây là cách để tránh phải trang trí mọi phương pháp danh sách. Nó làm cho PersistentList là context manager, vì vậy bạn có thể sử dụng cú pháp

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

. Phải thừa nhận rằng, điều này không làm cho phương thức _save được gọi sau mỗi thao tác danh sách, chỉ khi bạn thoát khỏi with-block. Nhưng tôi nghĩ nó cung cấp cho bạn đủ quyền kiểm soát để tiết kiệm khi bạn muốn tiết kiệm, đặc biệt là vì phương pháp __exit__ được đảm bảo sẽ được thực thi bất kể bạn rời khỏi with-block, bao gồm cả việc xảy ra vì ngoại lệ.

Bạn có thể là lợi thế mà _save không được gọi sau mỗi thao tác danh sách. Hãy tưởng tượng phụ thêm vào danh sách 10.000 lần. Vì vậy, nhiều cuộc gọi cá nhân đến db.set (một cơ sở dữ liệu?) Có thể khá tốn thời gian. Tôi sẽ tốt hơn, ít nhất là từ một quan điểm thực hiện, để làm cho tất cả các phụ thêm và tiết kiệm một lần.


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

Nếu bạn muốn, bạn có thể nhận được nhiều hơn nữa lạ mắt bằng cách trộn hai kỹ thuật để bạn giữ một lá cờ "bẩn" chỉ ra rằng nó cần phải được lưu lại. Bạn thậm chí có thể làm cho nó để 'PersistenList .__ del__' sẽ khiếu nại hoặc cố gắng để tiết kiệm (nếu hệ thống là thoát nó có thể thất bại) nếu nó là bẩn. –

+0

@ChrisMorgan: Tôi thích ý tưởng của bạn, nhưng tôi nghĩ sẽ rất khó để triển khai chính xác. Ví dụ, nếu một người dùng là 'append' sau đó' pop', việc thực hiện ngây thơ (bằng cách trang trí từng phương thức danh sách) sẽ đặt cờ 'dirty' sai. Để làm tốt hơn, bạn cần phải lưu một bản sao của danh sách trong '__enter__' và trong mỗi phương pháp danh sách kiểm tra nếu danh sách bị bẩn. Tất cả những so sánh đó có thể làm cho hiệu năng chậm. Vì nói chung bạn muốn tiết kiệm, có lẽ tốt hơn là một chút lãng phí và chỉ cần tiết kiệm mỗi lần. – unutbu

+0

Tôi chỉ đưa nó vào như một chỉ báo cơ bản mà mọi thứ đã được thay đổi. Chắc chắn, những thay đổi có thể đã được hoàn tác, nhưng như bạn nói, chi phí ngăn chặn việc viết không cần thiết sẽ là quá cao. –

0

Dưới đây là một câu trả lời đó là rất nhiều như @ của unutbu, nhưng tổng quát hơn. Nó cung cấp cho bạn một chức năng mà bạn có thể gọi để đồng bộ hóa đối tượng của bạn vào đĩa, và nó hoạt động với các lớp học có thể pickle khác bên cạnh 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__ 

Dưới đây là đoạn code mà làm cho rằng có thể:

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() 
Các vấn đề liên quan