Python 如何检测字典中的任何元素是否发生更改?

Python 如何检测字典中的任何元素是否发生更改?,python,dictionary,Python,Dictionary,而不是保存字典的副本,并比较新旧版本,如下所示: dict = { "apple":10, "pear":20 } if ( dict_old != dict ): do something dict_old = dict 如何检测字典中的任何元素何时发生更改?您可以将dict子类化,并包含一些自定义的\uuuuuuu setitem\uuuuu行为: class MyDict(dict): def __setitem__(self, item, value):

而不是保存字典的副本,并比较新旧版本,如下所示:

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

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

如何检测字典中的任何元素何时发生更改?

您可以将
dict
子类化,并包含一些自定义的
\uuuuuuu setitem\uuuuu
行为:

class MyDict(dict):
    def __setitem__(self, item, value):
        print "You are changing the value of %s to %s!!"%(item, value)
        super(MyDict, self).__setitem__(item, value)
用法示例:

In [58]: %cpaste
Pasting code; enter '--' alone on the line to stop or use Ctrl-D.
:class MyDict(dict):
:    def __setitem__(self, item, value):
:        print "You are changing the value of %s to %s!!"%(item, value)
:        super(MyDict, self).__setitem__(item, value)
:--

In [59]: d = MyDict({"apple":10, "pear":20})

In [60]: d
Out[60]: {'apple': 10, 'pear': 20}

In [61]: d["pear"] = 15
You are changing the value of pear to 15!!

In [62]: d
Out[62]: {'apple': 10, 'pear': 15}
您只需更改
print
语句,以包含修改时需要执行的任何检查

相反,如果您询问如何检查是否修改了特定变量名,那么这是一个更棘手的问题,尤其是如果修改没有在对象的上下文或可以专门监视它的上下文管理器中发生

在这种情况下,您可以尝试修改
globals
locals
指向的
dict
(取决于您希望在其中发生这种情况的范围),并将其关闭,例如,上面类似
MyDict
的实例,除了自定义创建的
\uuuuu setitem\uuuuuu
之外,您只需检查正在更新的项是否与要检查的变量名匹配。然后就好像你有一个后台“观察者”在监视变量名的变化


然而,这是一件非常糟糕的事情。首先,这将涉及一些严重的
本地人
全球人
,这通常不太安全。但也许更重要的是,通过创建一些容器类并在其中创建自定义更新/检测代码,这更容易实现。

如果只想检测其中的更改,则无需子类:

dict1==dict2


我会给你分类的

比@EMS更进一步

子类
dict
,另外添加一个
sentinal
属性来跟踪更改,并添加一个方法来通知您是否有任何更改

class MyDict(dict):
    def __init__(self):
        super(MyDict, self).__init__
        self.sentinal = list()
    def __setitem__(self, item, value):
        self.sentinal.append(item)
        super(MyDict, self).__setitem__(item, value)
    def __getitem__(self, item):
        self.sentinal.remove(item)
        return super(MyDict, self).__getitem__(item)
    def update(self, iterable):
        super(MyDict, self).update(iterable)
        self.sentinal.extend(k for k, v in iterable)
    def items(self):
        self.sentinal = list()
        return super(MyDict, self).items()
    def iteritems(self):
        self.sentinal = list()
        return super(MyDict, self).iteritems()
    def item_changed(self):
        return bool(self.sentinal), self.sentinal

>>> d = MyDict()
>>> d.update(((i, i*i) for i in xrange(5)))
>>> d
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
>>> d[1] = 'g'
>>> d.item_changed()
(True, [1])
>>> z = d[1]
>>> d.item_changed()
(False, [])
>>> d[3] = 'b'
>>> d[4] = 'foo'
>>> d
{0: 0, 1: 'g', 2: 4, 3: 'b', 4: 'foo'}
>>> d.item_changed()
(True, [3, 4])
>>> d.items()
[(0, 0), (1, 'g'), (2, 4), (3, 'b'), (4, 'foo')]
>>> d.item_changed()
(False, [])
>>> d.update([(0, 'bar'), (2, 'baz')])
>>> d
{0: 'bar', 1: 'g', 2: 'baz', 3: 'b', 4: 'foo'}
>>> d.item_changed()
(True, [0, 2])
>>> list(d.iteritems())
foo
[(0, 'bar'), (1, 'g'), (2, 'baz'), (3, 'b'), (4, 'foo')]
>>> d.item_changed()
(False, [])
>>> 

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

下面的代码应该是不言自明的。它应该适用于嵌套的dict和list

"""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
观察者描述符类允许在观察到对象的内容时触发任何任意操作 数据更改。 """ 进口武器 类观察者(对象): “”“观察附加的数据,并在数据内容更改时触发给定的操作。”。 Observer是一个描述符,这意味着它必须在类定义级别声明。 例子: >>>def操作(观察者、实例、值): “打印”数据已被修改:%s“%s”值 >>>类MyClass(对象): …重要数据=观察者('初始值',回调=操作) >>>o=MyClass() >>>o.重要数据='新值' 数据已修改:新的_值 Observer应该使用任何类型的内置数据类型,但强烈建议使用“dict”和“list”。 例子: >>>类MyClass2(对象): …重要数据=观察者({},回调=操作) >>>o2=MyClass2() >>>o2.重要数据['key1']={'item1':'value1','item2':'value2'} 数据已修改:{'key1':{'item2':'value2','item1':'value1'} >>>o2.重要_数据['key1']['item1']=范围(5) 数据已被修改:{'key1':{'item2':'value2','item1':[0,1,2,3,4]} >>>o2.重要_数据['key1']['item1'][0]='first' 数据已被修改:{'key1':{'item2':'value2','item1':['first',1,2,3,4]} 下面是一个使用“Observer”作为基类的示例。 例子: >>>高级描述员(观察员): …定义操作(自身、实例、值): …logger=instance.get_logger() …logger.info(值) ... …定义初始化(自身,附加数据=无,**kwargs): …self.additional_data=附加_数据 ... …超级(高级描述者,自我)。\u初始化__( …回调=AdvancedDescriptor.action, …初始值={}, …附加_数据=附加_数据 ... ) """ def uuu init uuuu(self,init u value=None,callback=None,**kwargs): """ Args: init_值:数据的初始值(如果没有) 回调:回调函数,用于在数据内容发生更改时调用;的签名 回调应该是回调(观察者、实例、值),其中: observer是一个observer对象,附加了所有附加数据, 实例是实际数据所在的对象的实例, 值是数据本身。 **kwargs:使继承成为可能需要额外的参数。请参见上面的示例,以获得 想法,正确的继承应该是什么样子。 这里的主要挑战来自这样一个事实,类构造函数是在类方法中使用的, 当您想要更改派生类中的`\uuu init\uu`函数签名时,这是相当棘手的。 """ self.init_值=init_值 self.callback=回调 self.kwargs=kwargs self.kwargs.update({ “回调”:回调, }) self.\u值=无 self.\u实例\u到\u名称\u映射={} self.\u实例=无 self.\u parent\u observer=无 self.\u value\u parent=None 自我价值指数=无 @财产 def值(自身): “”“返回附加数据的内容。 """ 返回自我值 定义获取属性名称(自身、实例): “”“为了尊重DRY方法,我们试图找出描述符的原始名称,以及 将其用作实例变量以存储实际数据。 Args: 实例:对象的实例
checksum = make_hash(d)

def make_hash(d):
    check = ''
    for key in d:
        check += str(d[key])
    return hash(check)

if checksum != make_hash(d):
    print('Dictionary changed')
>>> import jsonfile
>>> class DoSomething(jsonfile.JSONFileRoot):
...   def on_change(self):
...     print("do something")
... 
>>> d = DoSomething({"apple": 10, "pear": 20})
>>> d.data["apple"] += 1
do something
>>> d.data
{'apple': 11, 'pear': 20}
>>> d.data["plum"] = 5
do something
>>> d.data
{'apple': 11, 'pear': 20, 'plum': 5}