Доступ к полному словарю при установке вложенного словарного элемента в Python - PullRequest
0 голосов
/ 28 января 2019

Я пытаюсь создать подкласс словаря, который позволяет создать словарь 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}}.Существует три варианта обновления значения в словаре (на данный момент игнорируются update и set):

  1. Я могу обновить ключи верхнего уровня А, и это прекрасно работает:
A['foo'] = 2
# B is now automatically {'observed': '{"foo": 2, "bar": {"foobar": 2}}'}
Я могу обновить весь вложенный словарь:
A['bar'] = {'foobar': 4}
# B is now automatically {'observed': '{"foo": 2, "bar": {"foobar": 4}}'}
Но если я редактирую вложенное значение с помощью метода [], self в __setitem__ - это вложенный дикт, а не весь словарь, с которым инициализируется класс ObservedDict, поэтому:
A['bar']['foobar'] = 4
# B is now {'observed': '{"foobar": 4}'}

Мой вопрос: как мне сохранить информацию о родительском словаре (то есть, используемом для инициализации класса), чтобы при установке значения в третьем случае словарь B обновлялся ивключить весь словарь A (в данном случае соответствующий случай 2)?

Ответы [ 2 ]

0 голосов
/ 28 января 2019

Одна вещь, которую вы можете сделать, чтобы упростить класс, - это экстернализировать поведение обновления 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)

Затем вы можете использовать его с лямбда-выражением:

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}}'}
0 голосов
/ 28 января 2019

ОК, так что, хотя я и пытался прикрепить родительский словарь к вложенным словарям раньше, без удачи, комментарий @ 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), похоже, делает свое дело.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...