【问题标题】:Accessing full dictionary when setting a nested dictionary item in Python在 Python 中设置嵌套字典项时访问完整字典
【发布时间】:2019-06-20 22:31:26
【问题描述】:

我正在尝试创建一个字典子类,它允许创建字典 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 类的整个字典,所以:
A['bar']['foobar'] = 4
# B is now {'observed': '{"foobar": 4}'}

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

【问题讨论】:

  • 当创建嵌套的ObservedDict 时,它需要一个额外的参数来告诉它它的父字典。然后,嵌套的 dict 应该只通知其父级有关更改,以便父级(或者可能是进一步向上的祖先)正确处理更改。
  • @jonrsharpe 这是一个ObservedDict(即我的子类),但是是的,它与A 无关,因此我无法从A['bar'] 访问A。但这是我的问题:如何将 A['bar'](和任何其他通用嵌套字典)与 A 连接起来?

标签: python dictionary subclass observer-pattern


【解决方案1】:

为了使类更简单,您可以做的一件事是将更新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}}'}

【讨论】:

  • 谢谢!我非常喜欢儿童班选项。不过,我无法理解notify 的工作原理。在嵌套字典的情况下,它调用self.notify(self.notify)。那是如何过滤的,例如拉姆达函数?另外,如果我删除updated=None,我可以看到它会引发异常,但我看不到附加参数是什么......
  • 在嵌套的字典中,它本质上是调用self.notify(parent.notify),因为作为父父的每个字典都将自己的self.notify(绑定到自身)传递给它创建的ObservedDicts。任何调用都会传播到最顶层,它要么从外部收到 on_changed,要么实际上是覆盖 notify 的子类的实例。
  • updated 参数是必需的,因为我们希望 lambda 将实例作为参数。我们想要这个是因为如果我们不将它作为参数传递,我们将无法像示例 1 中那样从内联 lambda 引用实例本身(第一次调用 lambda 时尚未定义A)。我们给它一个默认参数,因为我们希望能够在不传递 self 的情况下调用 self.notify() - 因为将 self 传递给自己的方法看起来有点傻。如果您将self.on_changed(self) 更改为self.on_changed(),则可以摆脱它。
【解决方案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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-28
    • 2021-11-09
    • 2018-02-07
    • 2018-02-09
    • 2013-09-29
    • 1970-01-01
    相关资源
    最近更新 更多