如何检测字典中的任何元素是否发生变化? [英] How to detect if any element in a dictionary changes?

查看:89
本文介绍了如何检测字典中的任何元素是否发生变化?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

而不是保存字典的重复,而是将旧字典与新字典进行比较,就像这样:

Rather than saving a duplicate of the dictionary and comparing the old with the new, alike this:

dict = { "apple":10, "pear":20 }

if ( dict_old != dict ):
   do something
   dict_old = dict

当字典中的任何元素发生变化时如何检测?

推荐答案

您可以创建一个观察者,该观察者将监视数据内容是否已更改.

You could create an observer, which will monitor if the content of data has been changed.

下面的代码应该是不言自明的.它应该适用于嵌套的字典和列表.

The code below should be quite self-explanatory. It should work for nested dicts and lists.

"""Observer descriptor class allows to trigger out any arbitrary action, when the content of observed
data changes.
"""

import weakref


class Observer(object):
    """Observes attached data and trigger out given action if the content of data changes.
    Observer is a descriptor, which means, it must be declared on the class definition level.

    Example:
        >>> def action(observer, instance, value):
        ...     print 'Data has been modified: %s' % value

        >>> class MyClass(object):
        ...     important_data = Observer('init_value', callback=action)

        >>> o = MyClass()
        >>> o.important_data = 'new_value'
        Data has been modified: new_value


    Observer should work with any kind of built-in data types, but `dict` and `list` are strongly advice.

    Example:
        >>> class MyClass2(object):
        ...     important_data = Observer({}, callback=action)
        >>> o2 = MyClass2()
        >>> o2.important_data['key1'] = {'item1': 'value1', 'item2': 'value2'}
        Data has been modified: {'key1': {'item2': 'value2', 'item1': 'value1'}}
        >>> o2.important_data['key1']['item1'] = range(5)
        Data has been modified: {'key1': {'item2': 'value2', 'item1': [0, 1, 2, 3, 4]}}
        >>> o2.important_data['key1']['item1'][0] = 'first'
        Data has been modified: {'key1': {'item2': 'value2', 'item1': ['first', 1, 2, 3, 4]}}


    Here is an example of using `Observer` as a base class.

    Example:
        >>> class AdvanceDescriptor(Observer):
        ...     def action(self, instance, value):
        ...         logger = instance.get_logger()
        ...         logger.info(value)
        ...
        ...     def __init__(self, additional_data=None, **kwargs):
        ...         self.additional_data = additional_data
        ...
        ...         super(AdvanceDescriptor, self).__init__(
        ...             callback=AdvanceDescriptor.action,
        ...             init_value={},
        ...             additional_data=additional_data
        ...         )
    """

    def __init__(self, init_value=None, callback=None, **kwargs):
        """
        Args:
            init_value: initial value for data, if there is none
            callback: callback function to evoke when the content of data will change; the signature of
                the callback should be callback(observer, instance, value), where:
                    observer is an Observer object, with all additional data attached to it,
                    instance is an instance of the object, where the actual data lives,
                    value is the data itself.
            **kwargs: additional arguments needed to make inheritance possible. See the example above, to get an
                idea, how the proper inheritance should look like.
                The main challenge here comes from the fact, that class constructor is used inside the class methods,
                which is quite tricky, when you want to change the `__init__` function signature in derived classes.
        """
        self.init_value = init_value
        self.callback = callback
        self.kwargs = kwargs
        self.kwargs.update({
            'callback': callback,
        })

        self._value = None

        self._instance_to_name_mapping = {}
        self._instance = None

        self._parent_observer = None

        self._value_parent = None
        self._value_index = None

    @property
    def value(self):
        """Returns the content of attached data.
        """
        return self._value

    def _get_attr_name(self, instance):
        """To respect DRY methodology, we try to find out, what the original name of the descriptor is and
        use it as instance variable to store actual data.

        Args:
            instance: instance of the object

        Returns: (str): attribute name, where `Observer` will store the data
        """
        if instance in self._instance_to_name_mapping:
            return self._instance_to_name_mapping[instance]
        for attr_name, attr_value in instance.__class__.__dict__.iteritems():
            if attr_value is self:
                self._instance_to_name_mapping[weakref.ref(instance)] = attr_name
                return attr_name

    def __get__(self, instance, owner):
        attr_name = self._get_attr_name(instance)
        attr_value = instance.__dict__.get(attr_name, self.init_value)

        observer = self.__class__(**self.kwargs)
        observer._value = attr_value
        observer._instance = instance
        return observer

    def __set__(self, instance, value):
        attr_name = self._get_attr_name(instance)
        instance.__dict__[attr_name] = value
        self._value = value
        self._instance = instance
        self.divulge()

    def __getitem__(self, key):
        observer = self.__class__(**self.kwargs)
        observer._value = self._value[key]
        observer._parent_observer = self
        observer._value_parent = self._value
        observer._value_index = key
        return observer

    def __setitem__(self, key, value):
        self._value[key] = value
        self.divulge()

    def divulge(self):
        """Divulges that data content has been change calling callback.
        """
        # we want to evoke the very first observer with complete set of data, not the nested one
        if self._parent_observer:
            self._parent_observer.divulge()
        else:
            if self.callback:
                self.callback(self, self._instance, self._value)

    def __getattr__(self, item):
        """Mock behaviour of data attach to `Observer`. If certain behaviour mutate attached data, additional
        wrapper comes into play, evoking attached callback.
        """

        def observe(o, f):
            def wrapper(*args, **kwargs):
                result = f(*args, **kwargs)
                o.divulge()
                return result

            return wrapper

        attr = getattr(self._value, item)

        if item in (
                    ['append', 'extend', 'insert', 'remove', 'pop', 'sort', 'reverse'] + # list methods
                    ['clear', 'pop', 'update']                                           # dict methods
        ):
            return observe(self, attr)
        return attr


def action(self, instance, value):
    print '>> log >', value, '<<'


class MyClass(object):
    meta = Observer('', action)


mc1 = MyClass()
mc2 = MyClass()

mc1.meta = {
    'a1': {
        'a11': 'a11_val',
        'a22': 'a22_val',
    },
    'b1': 'val_b1',
}
mc1.meta['a1']['a11'] = ['1', '2', '4']
mc1.meta['a1']['a11'].append('5')
mc1.meta.update({'new': 'new_value'})

mc2.meta = 'test'
mc2.meta = 'test2'
mc2.meta = range(10)
mc2.meta[5] = 'test3'
mc2.meta[9] = {
    'a': 'va1',
    'b': 'va2',
    'c': 'va3',
    'd': 'va4',
    'e': 'va5',
}
mc2.meta[9]['a'] = 'val1_new'


class MyClass2(object):
    pkg = Observer('', action)


mc3 = MyClass2()
mc3.pkg = 'test_myclass2'
print mc1.meta.value

这篇关于如何检测字典中的任何元素是否发生变化?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆