通过属性和索引访问递归访问dict?

时间:2010-06-13 05:42:01

标签: python oop dictionary getattr

我希望能够做到这样的事情:

from dotDict import dotdictify

life = {'bigBang':
           {'stars':
               {'planets': []}
           }
       }

dotdictify(life)

# This would be the regular way:
life['bigBang']['stars']['planets'] = {'earth': {'singleCellLife': {}}}
# But how can we make this work?
life.bigBang.stars.planets.earth = {'singleCellLife': {}}

#Also creating new child objects if none exist, using the following syntax:
life.bigBang.stars.planets.earth.multiCellLife = {'reptiles':{},'mammals':{}}

我的动机是改进代码的简洁性,如果可能的话,使用与Javascript类似的语法来访问JSON对象,以实现高效的跨平台开发。 (我也使用Py2JS和类似的。)

5 个答案:

答案 0 :(得分:47)

这是创造这种体验的一种方式:

class DotDictify(dict):
    MARKER = object()

    def __init__(self, value=None):
        if value is None:
            pass
        elif isinstance(value, dict):
            for key in value:
                self.__setitem__(key, value[key])
        else:
            raise TypeError('expected dict')

    def __setitem__(self, key, value):
        if isinstance(value, dict) and not isinstance(value, DotDictify):
            value = DotDictify(value)
        super(DotDictify, self).__setitem__(key, value)

    def __getitem__(self, key):
        found = self.get(key, DotDictify.MARKER)
        if found is DotDictify.MARKER:
            found = DotDictify()
            super(DotDictify, self).__setitem__(key, found)
        return found

    __setattr__, __getattr__ = __setitem__, __getitem__


if __name__ == '__main__':

    life = {'bigBang':
               {'stars':
                   {'planets': {}  # Value changed from []
                   }
               }
           }

    life = DotDictify(life)
    print(life.bigBang.stars.planets)  # -> []
    life.bigBang.stars.planets.earth = {'singleCellLife' : {}}
    print(life.bigBang.stars.planets)  # -> {'earth': {'singleCellLife': {}}}

答案 1 :(得分:2)

下面是嵌套属性字典的另一个实现(受到Curt Hagenlocher的回答的启发,被剥离到必要的):

class AttrDict(dict):
    """ Nested Attribute Dictionary

    A class to convert a nested Dictionary into an object with key-values
    accessibly using attribute notation (AttrDict.attribute) in addition to
    key notation (Dict["key"]). This class recursively sets Dicts to objects,
    allowing you to recurse down nested dicts (like: AttrDict.attr.attr)
    """

    def __init__(self, mapping=None):
        super(AttrDict, self).__init__()
        if mapping is not None:
            for key, value in mapping.items():
                self.__setitem__(key, value)

    def __setitem__(self, key, value):
        if isinstance(value, dict):
            value = AttrDict(value)
        super(AttrDict, self).__setitem__(key, value)
        self.__dict__[key] = value  # for code completion in editors

    def __getattr__(self, item):
        try:
            return self.__getitem__(item)
        except KeyError:
            raise AttributeError(item)

    __setattr__ = __setitem__

这适用于Python 2和3:

life = AttrDict({'bigBang': {'stars': {'planets': {}}}})
life['bigBang']['stars']['planets'] = {'earth': {'singleCellLife': {}}}
life.bigBang.stars.planets.earth.multiCellLife = {'reptiles': {}, 'mammals': {}}
print(life.bigBang.stars.planets.earth)
# -> {'singleCellLife': {}, 'multiCellLife': {'mammals': {}, 'reptiles': {}}}

在Python3中需要将__getattr__中的KeyError转换为AttributeError,以便在找不到该属性的情况下hasattr也能正常工作:

hasattr(life, 'parallelUniverse')
# --> False

答案 2 :(得分:2)

有一个程序包可以完全满足您的需求,还可以做更多的事情,它称为Prodict

from prodict import Prodict

life_dict = {'bigBang':
                {'stars':
                    {'planets': []}
                }
            }

life = Prodict.from_dict(life_dict)

print(life.bigBang.stars.planets)
# prints []

# you can even add new properties dynamically
life.bigBang.galaxies = []

PS:我是《禁令》的作者。

答案 3 :(得分:0)

这是另一种解决方案:

from typing import Dict, Any

class PropertyTree: pass

def dict_to_prop_tree(yaml_config: Dict[str, Any]) -> PropertyTree:
    tree = PropertyTree()
    for key, value in yaml_config.items():
        if type(value) == dict:
            setattr(tree, key, dict_to_obj_tree(value))
        elif type(value) == list:
            setattr(tree, key, [dict_to_obj_tree(v) for v in value])
        else:
            setattr(tree, key, value)
    return tree

然后在python控制台中:

d={'a': 1, 'b': 2, 'c': {'d': 4, 'e': 5, 'f': {'g': 6}, 'h': {}, 'j': 7}}
tree=dict_to_prop_tree(d)
tree.a
tree.c.f.g

打印正确的值

答案 4 :(得分:0)

class AccessMode(dict):
    def __init__(self, mapping = None):
        super(AccessMode, self).__init__()
        if mapping is not None:
            if isinstance(mapping, dict):
                for k, v in mapping.items():
                    if isinstance(v, dict):
                        v = AccessMode(v)
                        self.__setitem__(k, v)
                    else:
                        print "TypeError: Input must be a 'dict' type.\n"

    def __setitem__(self, k, v):
        super(AccessMode, self).__setitem__(k, v)

    def __getitem__(self, k):
        return super(AccessMode,self).__getitem__(k)

    def __missing__(self, k):
        tmp = AccessMode()
        self[k] = tmp
        return tmp 

    __setattr__, __getattr__ = __setitem__, __getitem__