简体   繁体   English

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

[英]Accessing full dictionary when setting a nested dictionary item in Python

I'm trying to create a dictionary subclass which allows a dictionary A to be created which will update a value in a pre-exiting dictionary B to equal a string representation of dictionary A. I see it as an observer pattern, without the ability to have multiple objects observing.我正在尝试创建一个字典子类,它允许创建一个字典 A,它将更新预先存在的字典 B 中的值以等于字典 A 的字符串表示形式。我将其视为观察者模式,没有能力有多个对象观察。

ie: IE:

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 is now {'observed': '{"foo": 1, "bar": {"foobar": 2}}'} and A is {'foo': 1, 'bar': {'foobar': 2}} . B 现在是{'observed': '{"foo": 1, "bar": {"foobar": 2}}'}而 A 是{'foo': 1, 'bar': {'foobar': 2}} There are three cases for updating a value in the dictionary (ignoring update and set for now):更新字典中的值有三种情况(暂时忽略updateset ):

  1. I can update A's top-level keys, and it works just fine:我可以更新 A 的顶级键,它工作得很好:
A['foo'] = 2
# B is now automatically {'observed': '{"foo": 2, "bar": {"foobar": 2}}'}
  1. I can update the entirety of a nested dictionary:我可以更新整个嵌套字典:
A['bar'] = {'foobar': 4}
# B is now automatically {'observed': '{"foo": 2, "bar": {"foobar": 4}}'}
  1. But, if I edit a nested value by using the [] method, self in __setitem__ is the nested dict, not the whole dictionary with which the ObservedDict class is initialised, so:但是,如果我使用[]方法编辑嵌套值,则__setitem__中的self是嵌套字典,而不是初始化ObservedDict类的整个字典,因此:
A['bar']['foobar'] = 4
# B is now {'observed': '{"foobar": 4}'}

My question is: how do I retain information about the parent dictionary (ie the one used to initialise the class) such that on setting a value using the third case, dictionary B will update and include the whole of dictionary A (matching case 2, in this instance)?我的问题是:如何保留有关父字典(即用于初始化类的字典)的信息,以便在使用第三种情况设置值时,字典B将更新并包含整个字典 A(匹配情况 2,在这种情况下)?

OK, so although I had played around with attaching the parent dictionary to the nested dictionaries before, without luck, @MichaelButscher's comment spurred me to try again.好的,所以尽管我之前尝试将父字典附加到嵌套字典中,但运气不佳,@MichaelButscher 的评论促使我再次尝试。 Below is a working solution, which seems to work for setting a value in a nested dictionary using the [] method, no matter the depth.下面是一个有效的解决方案,它似乎适用于使用[]方法在嵌套字典中设置值,无论深度如何。

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

Ensuring the 'parent' was always the self as given when initialising the object for the first time (ie A ) seems to do the trick.在第一次初始化对象(即A )时,确保“父母”始终是给定的self似乎可以解决问题。

One thing you can do to make the class simpler is externalize the behavior of updating B , like so:为了使类更简单,您可以做的一件事是将更新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)

Then you can use it with a lambda:然后你可以将它与 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)

Or with a 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)

both of which give you the expected result:两者都给你预期的结果:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM