2012-01-13 44 views
12

Tôi có một lớp nơi các cá thể của lớp này cần theo dõi các thay đổi đối với các thuộc tính của nó.Theo dõi các thay đổi đối với danh sách và từ điển trong python?

Ví dụ: obj.att = 2 sẽ là thứ dễ dàng theo dõi chỉ bằng cách ghi đè __setattr__ của obj.

Tuy nhiên, có sự cố khi thuộc tính tôi muốn thay đổi là chính đối tượng, như danh sách hoặc lệnh chính tả.

Tôi làm cách nào để có thể theo dõi những thứ như obj.att.append(1) hoặc obj.att.pop(2)?

Tôi đang nghĩ đến việc mở rộng danh sách hoặc các lớp từ điển, nhưng khỉ vá các trường hợp của những lớp học khi objobj.att được cả hai khởi tạo để obj được thông báo khi có những thứ như .append được gọi. Bằng cách nào đó, điều đó không cảm thấy rất thanh lịch. Một cách khác mà tôi có thể nghĩ là sẽ chuyển một thể hiện của obj vào khởi tạo danh sách, nhưng điều đó sẽ phá vỡ rất nhiều mã hiện có cộng với nó dường như thậm chí còn kém thanh lịch hơn so với phương thức trước đó.

Bất kỳ ý tưởng/đề xuất nào khác? Có một giải pháp đơn giản mà tôi đang thiếu ở đây?

+4

những gì về làm cho các đối tượng thực tập và cho phép truy cập từ bên ngoài chỉ trên các lớp getter và setter? – joni

+0

bạn có thể sắp xếp các đối tượng và so sánh các phiên bản được tuần tự hóa. –

+0

Tôi đồng ý với joni. Hãy xem xét Luật Demeter ở đây. Ngoài ra, không cho phép truy cập vào 'attr' trực tiếp nhưng cung cấp getters và setters tạo bản sao sâu. – Thomas

Trả lời

4

Tôi đã tò mò làm thế nào điều này có thể được thực hiện khi tôi nhìn thấy câu hỏi, đây là giải pháp tôi đã đưa ra. Không đơn giản như tôi muốn nhưng nó có thể hữu ích. Thứ nhất, đây là hành vi:

class Tracker(object): 
    def __init__(self): 
     self.lst = trackable_type('lst', self, list) 
     self.dct = trackable_type('dct', self, dict) 
     self.revisions = {'lst': [], 'dct': []} 


>>> obj = Tracker()   # create an instance of Tracker 
>>> obj.lst.append(1)   # make some changes to list attribute 
>>> obj.lst.extend([2, 3]) 
>>> obj.lst.pop() 
3 
>>> obj.dct['a'] = 5   # make some changes to dict attribute 
>>> obj.dct.update({'b': 3}) 
>>> del obj.dct['a'] 
>>> obj.revisions    # check out revision history 
{'lst': [[1], [1, 2, 3], [1, 2]], 'dct': [{'a': 5}, {'a': 5, 'b': 3}, {'b': 3}]} 

Bây giờ trackable_type() chức năng mà làm cho tất cả các khả năng này:

def trackable_type(name, obj, base): 
    def func_logger(func): 
     def wrapped(self, *args, **kwargs): 
      before = base(self) 
      result = func(self, *args, **kwargs) 
      after = base(self) 
      if before != after: 
       obj.revisions[name].append(after) 
      return result 
     return wrapped 

    methods = (type(list.append), type(list.__setitem__)) 
    skip = set(['__iter__', '__len__', '__getattribute__']) 
    class TrackableMeta(type): 
     def __new__(cls, name, bases, dct): 
      for attr in dir(base): 
       if attr not in skip: 
        func = getattr(base, attr) 
        if isinstance(func, methods): 
         dct[attr] = func_logger(func) 
      return type.__new__(cls, name, bases, dct) 

    class TrackableObject(base): 
     __metaclass__ = TrackableMeta 

    return TrackableObject() 

này về cơ bản sử dụng một metaclass để ghi đè lên mọi phương pháp của một đối tượng để thêm một số ghi chép sửa đổi nếu thay đổi đối tượng. Đây không phải là siêu thử nghiệm kỹ lưỡng và tôi đã không thử bất kỳ loại đối tượng khác bên cạnh listdict, nhưng có vẻ như làm việc tốt cho những người.

+0

Vì vậy, đây là bản chất của ý tưởng vá khỉ mà tôi có .. – Pwnna

+1

Vâng về cơ bản, tôi đã cố gắng tìm một cách đơn giản để làm điều đó trong đó một cách mà bạn sẽ không phải tạo các lớp con 'list' và' dict' riêng biệt và tìm ra phương thức nào bạn cần vá, rõ ràng những gì tôi nghĩ ra không đơn giản :) –

3

Bạn có thể tận dụng các lớp cơ sở trừu tượng trong mô-đun bộ sưu tập, thực thi dict và danh sách. Điều này mang lại cho bạn một giao diện thư viện chuẩn để mã hóa với một danh sách ngắn các phương thức để ghi đè, __getitem__, __setitem__, __delitem__, insert. Bọc các thuộc tính trong bộ điều hợp có thể theo dõi bên trong __getattribute__.

import collections 

class Trackable(object): 
    def __getattribute__(self, name): 
     attr = object.__getattribute__(self, name) 
     if isinstance(attr, collections.MutableSequence): 
      attr = TrackableSequence(self, attr) 
     if isinstance(attr, collections.MutableMapping): 
      attr = TrackableMapping(self, attr) 
     return attr 

    def __setattr__(self, name, value): 
     object.__setattr__(self, name, value) 
     # add change tracking 


class TrackableSequence(collections.MutableSequence): 
    def __init__(self, tracker, trackee): 
     self.tracker = tracker 
     self.trackee = trackee 

    # override all MutableSequence's abstract methods 
    # override the the mutator abstract methods to include change tracking 


class TrackableMapping(collections.MutableMapping): 
    def __init__(self, tracker, trackee): 
     self.tracker = tracker 
     self.trackee = trackee 

    # override all MutableMapping's abstract methods 
    # override the the mutator abstract methods to include change tracking 
2

Thay vì khỉ vá, bạn có thể tạo ra một lớp proxy:

  • Tạo một lớp proxy mà kế thừa từ dict/danh sách/bộ bất cứ điều gì
  • Intercept thiết lập thuộc tính, và nếu giá trị là một dict/list/set, bọc nó vào lớp proxy
  • Trong lớp proxy __getattribute__, hãy đảm bảo phương thức này được gọi trên loại gói, nhưng hãy cẩn thận theo dõi trước khi thực hiện.

Pro:

  • không thay đổi lớp

Côn:

  • bạn được giới hạn một số loại bạn biết và mong đợi
Các vấn đề liên quan