【问题标题】:Detect changes to a nested dictionary with Python使用 Python 检测对嵌套字典的更改
【发布时间】:2019-05-21 18:37:17
【问题描述】:

我在 SO 上看到过一些类似的问题,关于检测字典的更改并在字典更改时调用函数,例如:

这些示例使用观察者模式的变体或重载__setitem__,但所有这些示例都没有检测到嵌套字典值的变化。

例如,如果我有:

my_dict = {'a': {'b': 1}}
my_dict['a']['b'] = 2

不会检测到2 对元素['a']['b'] 的分配。

我想知道是否有一种优雅的方法可以检测不仅字典的基本元素而且嵌套字典的所有子元素的更改。

【问题讨论】:

  • 检测变化是什么意思?在您不知情的情况下如何进行更改?我认为这里还有一些您没有涉及的更广泛的方面。
  • my_dict['a']... 正在获取而不是设置项目,然后...['b'] = 2 正在设置孩子。孩子还必须具有自定义行为。
  • 如果您查看提供的示例链接,您就会明白我的意思。我认为这很清楚。例如,当分配my_dict['a']['b'] = 2 时,我希望能够检测到嵌套字典中的值['a']['b'] 发生了变化,并根据变化运行特定的函数。
  • 好吧,现在,你只是举一个使用普通字典的例子。大概你已经对它进行了子类化
  • 如第二个链接所示,我们为什么不递归地将所有嵌套字典转换为自定义MyDicts 子类化dict

标签: python dictionary


【解决方案1】:

根据here 中给出的答案,只需执行以下操作:

class MyDict(dict):
    def __setitem__(self, item, value):
        print("You are changing the value of {} to {}!!".format(item, value))
        super(MyDict, self).__setitem__(item, value)

然后:

my_dict = MyDict({'a': MyDict({'b': 1})})

my_dict['a']['b'] = 2

您正在将 b 的值更改为 2!

my_dict['a'] = 5

您正在将 a 的值更改为 5!

如果您想避免在每个嵌套级别手动调用 MyDict,一种方法是完全重载 dict 类。例如:

class MyDict(dict):
    def __init__(self,initialDict):
        for k,v in initialDict.items():
          if isinstance(v,dict):
            initialDict[k] = MyDict(v)
        super().__init__(initialDict)

    def __setitem__(self, item, value):
        if isinstance(value,dict):
          _value = MyDict(value)
        else:
          _value = value
        print("You are changing the value of {} to {}!!".format(item, _value))
        super().__setitem__(item, _value)

然后您可以执行以下操作:

# Simple initialization using a normal dict synthax
my_dict = MyDict({'a': {'b': 1}})

# update example
my_dict['c'] = {'d':{'e':4}}

您正在将 c 的值更改为 {'d': {'e': 4}}!!

my_dict['a']['b'] = 2
my_dict['c']['d']['e'] = 6

您正在将 b 的值更改为 2!

您正在将 e 的值更改为 6!

【讨论】:

  • 不错,也许直接将嵌套的字典转换为 MyDict 对象会很有趣(在value 上使用isinstance
  • 感谢您的回答,ma3oun - 这是一种方法,但是 Corentin Limier 所描述的将是一个更“优雅”的解决方案恕我直言。
  • 查看编辑:无需修改已构建字典的解决方案。但是,您需要添加其他 dict 构造函数以获得完整的解决方案,包括使用 kwargs
  • 谢谢,这是迄今为止最好的解决方案。看看我们是否可以确定变化的完整背景会很有趣。例如You are changing the value of ['c']['d']['e'] to 6!!.
  • 可以通过在构造函数中添加parent 属性轻松完成。您将根字典初始化为None,并在设置子字典项时将其填充为key(对于所有字典值,您还需要在__init__ 中设置它。
【解决方案2】:

完整的解决方案借用this链接(OP给出的第二个)

class MyDict(dict):
    def __setitem__(self, item, value):
        print("You are changing the value of {key} to {value}!!".format(key=item, value=value))
        super(MyDict, self).__setitem__(item, convert_to_MyDict_nested(value))

def convert_to_MyDict_nested(d):
    if not(isinstance(d, dict)):
        return d
    for k, v in d.items():
        if isinstance(v, dict):
            d[k] = convert_to_MyDict_nested(v)
    return MyDict(d)

如果

d = {'a': {'b': 1}}

那么,

d = convert_to_MyDict_nested(d)
d['a']['b'] = 2  # prints You are changing the value of b to 2!!
d['a']= 5  # prints You are changing the value of a to 5!!

另外,根据 OP 的评论进行了编辑。所以,

d["c"] = {"e" : 7}  # prints You are changing the value of c to {'e': 7}!!
d["c"]["e"] = 9  # prints You are changing the value of e to 9!!

【讨论】:

  • 有没有办法在 MyDict 类内部执行此操作?我问的原因是如果我们设置d['a'] = {'b': 6} 然后执行d['a']['b'] = 7,第二个分配将不会被捕获,因为它不是MyDict 类型。
  • 是的,我们更改为super(MyDict, self).__setitem__(item, convert_to_MyDict_nested(value)),这样,所有的字典都会放在初始的 MyDict 和 MyDicts 中。
【解决方案3】:

您可以编写一个自动包装自身值的适配器类,如下所示(未经测试,但我认为它说明了这一点):

class DictChangeListenerAdapter:
    def __init__(self, original, listener):
        self._original = original
        self._listener = listener

    def __getitem__(self, key):
        value = self._original.__getitem__(key)
        if isinstance(value, dict):
            return DictChangeListenerAdapter(self._original[key], self._listener)
        else:
            return value

    def __setitem__(self, key, value):
        self._original.__setitem__(key, value)
        self._listener(self, key, value)

请注意,这将导致使用包装物品的成本更高,请谨慎使用。

【讨论】:

  • 谢谢你,虽然打印父字典并没有给出字典而是适配器对象..? <__main__.DictChangeListenerAdapter object at 0x10dd867f0>
  • 您需要在打印前先打开它。或者你可以实现__repr__ 并将其转发到底层字典。
猜你喜欢
  • 2012-09-16
  • 1970-01-01
  • 1970-01-01
  • 2019-12-04
  • 1970-01-01
  • 2022-11-15
  • 1970-01-01
  • 2022-06-13
  • 2021-04-14
相关资源
最近更新 更多