Вложенный словарь в объектно-подобный dict со свойствами - PullRequest
1 голос
/ 05 марта 2020

скажем, у меня есть две (простые игрушки) вложенные структуры данных, подобные этой:

d = dict(zip(list('abc'), list(range(3))))
nested_dict = {k:d.copy() for k in d}
nested_listof_dict = {k:[d.copy() for _ in range(3)] for k in d} 

Теперь я хочу, чтобы это вело себя как «обычный» классоподобный объект (имеется в виду точка-индексируемость)

class dictobj(dict):
    def __init__(self, data: dict, name):
        data['_name'] = name
        super().__init__(data)
        for name, item in data.items():
            if isinstance(item, (list, tuple)):
                setattr(self, name, [dictobj(x, name) if isinstance(x, dict) else x for x in item])
            else:
                setattr(self, name, dictobj(item, name) if isinstance(item, dict) else item)

    def __repr__(self):
        return f"{self['_name']}"


data_dictobj = dictobj(data, 'test')  # size 1185 bytes

, который хорошо работает как для вложенного dict, так и для nested_listof_dict

assert nested_listof_dict.a[0].b == nested_listof_dict['a'][0]['b']

, но поскольку атрибуты и словари являются изменяемыми, это может произойти

nested_listof_dict['a'][0]['b'] = 2
assert nested_listof_dict.a[0].b != nested_listof_dict['a'][0]['b']  # unwanted behavior

поэтому было бы неплохо реализовать атрибуты как свойства. Я подумал, что было бы неплохо избегать использования лямбда-функций из-за замыкания замыкания. Сначала рассмотрев реализацию геттера, я сосредоточился на nested_dict, поскольку это более простая структура.

class dictobj(dict):

    def __init__(self, data: dict, name):

        def make_property(self, name, item):
            def getter(self):
                return dictobj(item, name) if isinstance(item, dict) else item
            setattr(self.__class__, name, property(getter))
            # def setter(self, value):
            #     if not isinstance(value, type(item)):
            #         raise ValueError(f'cannot change the data structure, expected '+
            #                      f'{type(item).__name__} got {type(value).__name__}')
            #     self[name] = value
            # setattr(self.__class__, name, property(getter, setter))

        data['_name'] = name
        super().__init__(data)
        for name, item in data.items():
            if isinstance(item, (list, tuple)):
                setattr(self, name, [dictobj(x, name) if isinstance(x, dict) else x for x in item])
            else:
                make_property(self, name, item)

    def __repr__(self):
        return f"{self['_name']}"

, а затем проверьте, нельзя ли установить атрибут больше

d = dictobj(d, 'test')
# d.a = 1  # fails as should: "AttributeError: can't set attribute"
# d.a.a = 1  # fails as should: "AttributeError: can't set attribute"

Но почему-то я все еще не в порядке, наблюдается следующее поведение:

print(d.a)  # returns object "a" - as desired
print(d.a)  # returns 0 - second call returns the nested value

Я не знаю, как избежать этого поведения. Кроме того, я также хотел бы создать сеттер, который обеспечивает поддержку структуры данных. Не удивляя комментатора, которого я написал выше, неудивительно, что это также приводит к непреднамеренному поведению

d.a = {1}  # ValueError: cannot change the data structure, expected dict got set - as desired
d.a.a = 2  # AttributeError: 'int' object has no attribute 'a'
d.a = 2 
assert d.a == 0 and d['a'] == 2  # again unintended

Я хотел бы понять, что я делаю неправильно, и заставить это работать. Также следует отметить, что я еще даже не рассматривал создание свойств для nested_listof_dict, которые также были бы необходимы.

1 Ответ

1 голос
/ 05 марта 2020

munch делает именно то, что мне нужно

...