2012-01-13 12 views
12

私はこのクラスのインスタンスがその属性の変更を追跡する必要があるクラスを持っています。Pythonでリストや辞書の変更を追跡しますか?

例:obj.att = 2は、__setattr__objに上書きするだけで簡単に追跡できます。

しかし、変更したい属性がリストや辞書のようにオブジェクトそのものである場合に問題があります。

obj.att.append(1)またはobj.att.pop(2)のようなものをどのようにトラッキングできますか?

私はリストや辞書のクラスを拡張すると思っていますが、サルはobjたら、それらのクラスのインスタンスにパッチを適用してobj.attは両方.appendのようなものが呼び出されたときobjが通知されますので、初期化されます。どういうわけか、それは非常にエレガントな気がしません。

他の方法は、objのインスタンスをリストの初期化に渡すことですが、それは多くの既存のコードを破るだけでなく、以前の方法よりもあまり優雅ではないようです。

他のアイデアや提案はありますか?私がここで欠けている単純な解決策はありますか?

+4

どの程度期待していませんオブジェクトをインターンにして許可するゲッターとセッターのクラスだけでは外部からのアクセスですか? – joni

+0

オブジェクトをシリアル化してシリアル化されたバージョンを比較することができます。 –

+0

私はjoniに同意します。ここではデメテルの法則について考えてみましょう。あるいは、 'attr'へのアクセスを直接禁止するが、ディープコピーを作成するgetterとsetterを提供する。 – Thomas

答えて

4

私は質問を見たときにこれがどのように達成されるのか不思議でしたが、私が思いついた解決策はここにあります。私が望むほどシンプルではありませんが、役に立つかもしれません。まず、ここでの動作は次のとおりです。

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}]} 

今、このすべてを可能にするtrackable_type()機能:

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

これは基本的にあれば、いくつかのリビジョンのログを追加するには、オブジェクトのすべてのメソッドをオーバーライドするメタクラスを使用していますオブジェクトが変更されます。これは徹底的にテストされたスーパーではないと私はlistdict以外の他のオブジェクトタイプを試していないが、それらのために大丈夫と思われる。

+0

これは本質的に私が持っている猿のパッチのアイデアです。 – Pwnna

+1

ええ、基本的に、私はそうした簡単な方法で個別の 'list'サブクラスと' dict'サブクラスを作成する必要がなく、どのメソッドをパッチする必要があるのか​​分かりませんが、明らかに私が思いついたのはそれほど簡単ではありません:) –

3

dictとlistの実装であるcollectionsモジュールの抽象基本クラスを利用できます。これにより、オーバーライドするメソッドの短いリスト__getitem__, __setitem__, __delitem__, insertでコード化するための標準ライブラリインターフェイスが提供されます。 __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

  • 値であれば何でも
  • インターセプト属性設定のdict /リスト/セットを継承するプロキシクラスを作成し、 dict/list/setをプロキシクラスにラップする
  • プロキシクラス__getattribute__では、メソッドがラップされた型で呼び出されていることを確認します。

プロ:

  • 何のクラス変更は

コン:あなたが知っているとの種類の数に制限されている

関連する問題