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

时间:2019-01-27 21:53:40

标签: python dictionary subclass observer-pattern

我正在尝试创建一个字典子类,该子类允许创建字典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. 但是,如果我使用[]方法编辑嵌套值,则self中的__setitem__是嵌套字典,而不是ObservedDict类所使用的整个字典已初始化,所以:
A['bar']['foobar'] = 4
# B is now {'observed': '{"foobar": 4}'}

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

2 个答案:

答案 0 :(得分:0)

好的,所以尽管我以前曾尝试过将父词典附加到嵌套字典上,但没有运气,@ 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

确保“父母”始终是self,就像第一次初始化对象(即A)时给出的那样。

答案 1 :(得分:0)

您可以使类更简单的一件事是外部化更新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)

或带有子类

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}}'}