在 Python 中设置嵌套字典项时访问完整字典

Accessing full dictionary when setting a nested dictionary item in Python

我正在尝试创建一个字典子class,它允许创建一个字典 A,它将更新一个预先存在的字典 B 中的值,使其等于字典 A 的字符串表示。我明白了它作为一个观察者模式,没有观察多个对象的能力。

即:

import json
from collections import Mapping


class ObservedDict(dict):

    def __init__(self, initial_dict, name=None, observer=None, top_level=True):
        for k, v in initial_dict.items():
            if isinstance(v, dict):
                initial_dict[k] = ObservedDict(v, name, observer, top_level=False)

        super().__init__(initial_dict)

        self.name = name
        self.observer = observer
        if top_level is True:  # initialise the key:value pair in B
            observer[name] = json.dumps(initial_dict)

    def __setitem__(self, item, value):
        if isinstance(value, dict):
            _value = ObservedDict(value, self.name, self.observer, top_level=False)
        else:
            _value = value

        super().__setitem__(item, _value)
        # Update B
        self.observer[self.name] = json.dumps(self)

B = {}
A = ObservedDict({'foo': 1, 'bar': {'foobar': 2}}, 'observed', B)

B 现在是 {'observed': '{"foo": 1, "bar": {"foobar": 2}}'},A 是 {'foo': 1, 'bar': {'foobar': 2}}。更新字典中的值有三种情况(暂时忽略updateset):

  1. 我可以更新 A 的顶级密钥,而且效果很好:
A['foo'] = 2
# B is now automatically {'observed': '{"foo": 2, "bar": {"foobar": 2}}'}
  1. 我可以更新整个嵌套字典:
A['bar'] = {'foobar': 4}
# B is now automatically {'observed': '{"foo": 2, "bar": {"foobar": 4}}'}
  1. 但是,如果我使用 [] 方法编辑嵌套值,__setitem__ 中的 self 是嵌套字典,而不是包含 ObservedDict class 被初始化,所以:
A['bar']['foobar'] = 4
# B is now {'observed': '{"foobar": 4}'}

我的问题是:如何保留有关父字典(即用于初始化 class 的字典)的信息,以便在使用第三种情况设置值时,字典 B 将更新并包含整个字典 A(在本例中为匹配案例 2)?

好吧,尽管我之前尝试过将父词典附加到嵌套词典,但没有成功,@MichaelButscher 的评论促使我再次尝试。下面是一个可行的解决方案,它似乎适用于使用 [] 方法在嵌套字典中设置值,无论深度如何。

import json
from collections import Mapping


class ObservedDict(dict):

    def __init__(self, initial_dict, name=None, observer=None, parent=None):
        for k, v in initial_dict.items():
            if isinstance(v, dict):
                _parent = self if parent is None else parent
                initial_dict[k] = ObservedDict(v, name, observer, parent=_parent)

        super().__init__(initial_dict)

        self.observer = observer
        self.name = name
        self.parent = parent
        if parent is None:  # initialise the key:value pair in B
            observer[name] = json.dumps(initial_dict)

    def __setitem__(self, item, value):
        if isinstance(value, dict):
            _value = ObservedDict(value, self.name, self.observer, parent=self.parent)
        else:
            _value = value

        super().__setitem__(item, _value)

        # Update B
        if self.parent is not None:
            self.observer[self.name] = json.dumps(self.parent)  # nested dict
        else:
            self.observer[self.name] = json.dumps(self)  # the top-level dict

确保 'parent' 始终是第一次初始化对象时给出的 self(即 A)似乎可以解决问题。

为了使 class 更简单,您可以做的一件事是将更新 B 的行为外部化,如下所示:

class ObservedDict(dict):
    def __init__(self, initial_dict, on_changed=None):
        super().__init__(initial_dict)

        self.on_changed = on_changed

        for k, v in initial_dict.items():
            if isinstance(v, dict):
                super().__setitem__(
                    k, ObservedDict(v, on_changed=self.notify))

        self.notify()

    def __setitem__(self, key, value):
        if isinstance(value, dict):
            value = ObservedDict(value, on_changed=self.notify)
        super().__setitem__(key, value)
        self.notify()

    def notify(self, updated=None):
        if self.on_changed is not None:
            self.on_changed(self)

然后你可以将它与 lambda 一起使用:

import json


B = {}
A = ObservedDict(
        {'foo': 1, 'bar': {'foobar': 2}},
        lambda d: B.update({'observed': json.dumps(d)}))

print(B)
A['foo'] = 2
print(B)
A['bar'] = {'foobar': 4}
print(B)
A['bar']['foobar'] = 5
print(B)

或者用 child class

class UpdateObserverDict(ObservedDict):
    def __init__(self, *args, name, observer, **kwargs):
        self.observer = observer
        self.name = name
        super().__init__(*args, **kwargs)

    def notify(self, updated=None):
        self.observer[self.name] = json.dumps(self)

B = {}
A = UpdateObserverDict(
        {'foo': 1, 'bar': {'foobar': 2}},
        name='observed', observer=B)

print(B)
A['foo'] = 2
print(B)
A['bar'] = {'foobar': 4}
print(B)
A['bar']['foobar'] = 5
print(B)

两者都给你预期的结果:

{'observed': '{"foo": 1, "bar": {"foobar": 2}}'}
{'observed': '{"foo": 2, "bar": {"foobar": 2}}'}
{'observed': '{"foo": 2, "bar": {"foobar": 4}}'}
{'observed': '{"foo": 2, "bar": {"foobar": 5}}'}