跟踪python中列表和词典的更改?

时间:2012-01-13 23:14:32

标签: python

我有一个类,其中此类的实例需要跟踪其属性的更改。

示例:obj.att = 2只需覆盖__setattr__的{​​{1}}即可轻松追踪。

但是,当我想要更改的属性是一个对象本身,如列表或字典时,会出现问题。

我如何跟踪objobj.att.append(1)

等内容

我正在考虑扩展列表或字典类,但是在obj.att.pop(2)obj都被初始化后,猴子修补这些类的实例,以便在obj.att获得通知时调用obj之类的东西。不知何故,这感觉不是很优雅。

我能想到的另一种方法是将.append的实例传递给列表初始化,但这会破坏很多现有代码,而且它看起来甚至比以前的方法更优雅。

还有其他想法/建议吗?是否有一个我在这里缺少的简单解决方案?

5 个答案:

答案 0 :(得分:7)

我很好奇当我看到这个问题时如何实现这一点,这是我提出的解决方案。并不像我希望的那样简单但它可能有用。首先,这是行为:

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之外,我还没有尝试过任何其他对象类型,但它似乎对那些人没用。

答案 1 :(得分:4)

您可以利用集合模块中的抽象基类,该模块用于编写和列出工具。这为您提供了一个标准的库接口,可以使用一小段要覆盖的方法进行编码,__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 :(得分:3)

您可以创建代理类,而不是猴子修补:

  • 创建一个继承自dict / list / set whatever
  • 的代理类
  • 拦截属性设置,如果值是dict / list / set,请将其包装到代理类中
  • 在代理类__getattribute__中,确保在包装类型上调用该方法,但在执行此操作之前请先进行跟踪。

临:

  • 没有班级改变

缺点:

  • 您仅限于许多您认识和期待的类型

答案 3 :(得分:0)

我的jsonfile模块检测到(嵌套的)JSON兼容Python对象的更改。只需将JSONFileRoot子类化即可使更改检测适应您的需求。

>>> import jsonfile
>>> class Notify(jsonfile.JSONFileRoot):
...   def on_change(self):
...     print(f'notify: {self.data}')
... 
>>> test = Notify()
>>> test.data = 1
notify: 1
>>> test.data = [1,2,3]
notify: [1, 2, 3]
>>> test.data[0] = 12
notify: [12, 2, 3]
>>> test.data[1] = {"a":"b"}
notify: [12, {'a': 'b'}, 3]
>>> test.data[1]["a"] = 20
notify: [12, {'a': 20}, 3]

请注意,它在不支持集的情况下以e-satis建议的方式继续使用代理类。

答案 4 :(得分:0)

您还可以包装要跟踪的字典或列表方法,并确保在包装器内执行所需的操作。这是一个字典示例:

from functools import wraps

def _setChanged(func):
    @wraps(func)
    def wrappedFunc(self, *args, **kwargs):
        self.changed = True
        return func(self, *args, **kwargs)
    return wrappedFunc

def _trackObjectMethods(calssToTrack):
    for methodName in dir(calssToTrack):
        if methodName in calssToTrack._destructive:
            setattr(calssToTrack, methodName, _setChanged(getattr(calssToTrack, methodName)))

class Dictionary(dict):
    _destructive = ('__delitem__', '__setitem__', 'clear', 'pop', 'popitem', 'setdefault', 'update')

    def __init__(self, *args, **kwargs):
        self.changed = False
        super().__init__(*args, **kwargs)

_trackObjectMethods(Dictionary)

d = Dictionary()
print(d.changed)
d["1"] = 'test'
print(d.changed)
d.changed = False
print(d.changed)
d["12"] = 'test2'
print(d.changed)

如您所见,如果词典中的任何项目发生了变化,那么我添加到自定义词典对象中的自定义变量将被设置为True。这样,我就可以知道自上次将更改后的变量设置为False以来对象是否有更改。