Python:轻松访问深层嵌套的dict(get和set)

Hal*_*Hal 23 python

我正在构建一些Python代码来读取和操作深层嵌套的dicts(最终用于与JSON服务进行交互,但是出于其他目的它会很棒)我正在寻找一种方法来轻松读取/设置/更新内部值dict,不需要很多代码.

@see还有Python:通过属性递归访问dict以及索引访问? - Curt Hagenlocher的"DotDictify"解决方案非常有说服力.我也喜欢Ben Alman在http://benalman.com/projects/jquery-getobject-plugin/中为JavaScript提供的内容. 将两者结合起来会很棒.

在Curt Hagenlocher和Ben Alman的例子的基础上,拥有如下功能的Python会很棒:

>>> my_obj = DotDictify()
>>> my_obj.a.b.c = {'d':1, 'e':2}
>>> print my_obj
{'a': {'b': {'c': {'d': 1, 'e': 2}}}}
>>> print my_obj.a.b.c.d
1
>>> print my_obj.a.b.c.x
None
>>> print my_obj.a.b.c.d.x
None
>>> print my_obj.a.b.c.d.x.y.z
None
Run Code Online (Sandbox Code Playgroud)

知道这是否可行,如果可行,如何修改DotDictify解决方案?

或者,可以使get方法接受点符号(并添加补充集方法)但是对象符号确定更清晰.

>>> my_obj = DotDictify()
>>> my_obj.set('a.b.c', {'d':1, 'e':2})
>>> print my_obj
{'a': {'b': {'c': {'d': 1, 'e': 2}}}}
>>> print my_obj.get('a.b.c.d')
1
>>> print my_obj.get('a.b.c.x')
None
>>> print my_obj.get('a.b.c.d.x')
None
>>> print my_obj.get('a.b.c.d.x.y.z')
None
Run Code Online (Sandbox Code Playgroud)

这种类型的交互对于处理深层嵌套的dicts非常有用.有人知道另一种策略(或示例代码片段/库)吗?

Mik*_*one 33

属性树

你的第一个规范的问题是,Python不能诉说__getitem__,如果在my_obj.a.b.c.d,你接下来会进一步继续向下一个不存在的树,在这种情况下,需要用返回一个对象__getitem__的方法,所以你不会得到AttributeError你抛出,或者如果你想要一个值,在这种情况下它需要返回None.

我认为,无论你在上面的每一种情况下,都应该期待它抛出KeyError而不是返回None.原因是你无法分辨是否None意味着"无钥匙"或"某人实际存放None在该位置".对于此行为,您所要做的就是使用dotdictify,删除marker和替换__getitem__:

def __getitem__(self, key):
    return self[key]
Run Code Online (Sandbox Code Playgroud)

因为你真正想要的是一个dict__getattr____setattr__.

可能有一种方法可以__getitem__完全删除并说出类似的内容__getattr__ = dict.__getitem__,但我认为这可能是过度优化,如果您以后决定要__getitem__dotdictify最初那样创建树,那将会成为问题,在这种情况下,您会将其更改为:

def __getitem__(self, key):
    if key not in self:
        dict.__setitem__(self, key, dotdictify())
    return dict.__getitem__(self, key)
Run Code Online (Sandbox Code Playgroud)

我不喜欢marker原来的生意dotdictify.

路径支持

第二个规范(覆盖get()set())是法线dictget()运行方式与您描述的不同,甚至没有set(尽管它具有与之setdefault()相反的操作get()).人们期望get采用两个参数,如果找不到密钥则第二个是默认参数.

如果要扩展__getitem____setitem__处理虚线键表示法,则需要修改doctictify为:

class dotdictify(dict):
    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 '.' in key:
            myKey, restOfKey = key.split('.', 1)
            target = self.setdefault(myKey, dotdictify())
            if not isinstance(target, dotdictify):
                raise KeyError, 'cannot set "%s" in "%s" (%s)' % (restOfKey, myKey, repr(target))
            target[restOfKey] = value
        else:
            if isinstance(value, dict) and not isinstance(value, dotdictify):
                value = dotdictify(value)
            dict.__setitem__(self, key, value)

    def __getitem__(self, key):
        if '.' not in key:
            return dict.__getitem__(self, key)
        myKey, restOfKey = key.split('.', 1)
        target = dict.__getitem__(self, myKey)
        if not isinstance(target, dotdictify):
            raise KeyError, 'cannot get "%s" in "%s" (%s)' % (restOfKey, myKey, repr(target))
        return target[restOfKey]

    def __contains__(self, key):
        if '.' not in key:
            return dict.__contains__(self, key)
        myKey, restOfKey = key.split('.', 1)
        target = dict.__getitem__(self, myKey)
        if not isinstance(target, dotdictify):
            return False
        return restOfKey in target

    def setdefault(self, key, default):
        if key not in self:
            self[key] = default
        return self[key]

    __setattr__ = __setitem__
    __getattr__ = __getitem__
Run Code Online (Sandbox Code Playgroud)

测试代码:

>>> life = dotdictify({'bigBang': {'stars': {'planets': {}}}})
>>> life.bigBang.stars.planets
{}
>>> life.bigBang.stars.planets.earth = { 'singleCellLife' : {} }
>>> life.bigBang.stars.planets
{'earth': {'singleCellLife': {}}}
>>> life['bigBang.stars.planets.mars.landers.vikings'] = 2
>>> life.bigBang.stars.planets.mars.landers.vikings
2
>>> 'landers.vikings' in life.bigBang.stars.planets.mars
True
>>> life.get('bigBang.stars.planets.mars.landers.spirit', True)
True
>>> life.setdefault('bigBang.stars.planets.mars.landers.opportunity', True)
True
>>> 'landers.opportunity' in life.bigBang.stars.planets.mars
True
>>> life.bigBang.stars.planets.mars
{'landers': {'opportunity': True, 'vikings': 2}}
Run Code Online (Sandbox Code Playgroud)


Ehv*_*nce 5

对于谷歌员工:我们现在有瘾君子

pip install addict
Run Code Online (Sandbox Code Playgroud)

mapping.a.b.c.d.e = 2
mapping
{'a': {'b': {'c': {'d': {'e': 2}}}}}
Run Code Online (Sandbox Code Playgroud)

我广泛地使用它。

为了使用虚线路径,我发现dotted

obj = DottedDict({'hello': {'world': {'wide': 'web'}}})
obj['hello.world.wide'] == 'web'  # true
Run Code Online (Sandbox Code Playgroud)


Mah*_*emi 5

较旧的答案中有一些非常好的提示,但它们都需要用自定义数据结构替换标准 Python 数据结构(dicts 等),并且不适用于不是有效属性名称的键。

这些天我们可以做得更好,使用纯 Python、Python 2/3 兼容库,专门为此目的而构建,称为glom。使用您的示例:

import glom

target = {}  # a plain dictionary we will deeply set on
glom.assign(target, 'a.b.c', {'d': 1, 'e': 2}, missing=dict)
# {'a': {'b': {'c': {'e': 2, 'd': 1}}}}
Run Code Online (Sandbox Code Playgroud)

注意missing=dict, 用于自动创建字典。我们可以使用 glom 的 deep-get 轻松获取值:

glom.glom(target, 'a.b.c.d')
# 1
Run Code Online (Sandbox Code Playgroud)

你可以用glom做更多的事情,尤其是在深度获取和设置方面。我应该知道,因为(完全披露)我创建了它。这意味着如果你发现一个缺口,你应该让我知道