首页 > 解决方案 > 嵌套字典到具有属性的类似对象的字典

问题描述

假设我有两个(简单的玩具)嵌套数据结构,如下所示:

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

它适用于嵌套字典和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

因此,将属性实现为属性是一个好主意。我认为由于闭包作用域而避免使用 lambda 函数可能是一个好主意。首先看如何实现 getter,我关注的是 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

我不知道如何避免这种行为发生。除此之外,我还想生成一个强制维护数据结构的设置器。毫不奇怪,取消注释我上面写的 setter 也会产生意想不到的行为

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 生成属性,这也是需要的。

标签: pythondictionarydynamicpropertiesnested

解决方案


蒙克正是我需要的


推荐阅读