Python:轻松访问深度嵌套的字典(获取和设置)

新手上路,请多包涵

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

@see also Python: Recursively access dict via attributes 以及索引访问? -- 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

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

或者,可以使 get 方法接受点表示法(并添加一个互补的 set 方法),但是对象表示法肯定更清晰。

 >>> 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

这种类型的交互对于处理深度嵌套的指令非常有用。有人知道要尝试的另一种策略(或示例代码片段/库)吗?

原文由 Hal 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.2k
3 个回答

属性树

您的第一个规范的问题是 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]

因为你真正想要的是 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)

我不喜欢原来的 marker 业务 dotdictify

路径支持

The second specification (override get() and set() ) is that a normal dict has a get() that operates differently from what you describe and doesn甚至没有 set (虽然它有 setdefault() 这是 get() 的逆运算)。人们期望 get 采用两个参数,第二个参数是未找到密钥时的默认值。

如果你想扩展 __getitem____setitem__ 来处理点分键表示法,你需要修改 doctictify :d2–

 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__

测试代码:

 >>> 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}}

原文由 Mike DeSimone 发布,翻译遵循 CC BY-SA 2.5 许可协议

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

如今,我们可以做得更好,使用纯 Python、Python 23 兼容的库,正是为此目的而构建的,称为 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}}}}

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

 glom.glom(target, 'a.b.c.d')
# 1

您可以使用 glom 做更多的事情,尤其是在深度获取和设置方面。我应该知道,因为(完全公开)我创造了它。这意味着如果你发现差距,你应该 让我知道

原文由 Mahmoud Hashemi 发布,翻译遵循 CC BY-SA 4.0 许可协议

新手上路,请多包涵
dic = {"a1": {"a2": {"a3": {"a4": {"a5": {"a6": "6", "a7": "7"}}}}}}


def JSON_Msg_Read_and_Update(Act, JSON, Keys, Data):
    # Act 操作 , 'r'读取 , 'u'更新
    # JSON 原始数据
    # Keys 要查询的Key列表 , ['Key1','Key2,'......]
    # Data是需修改的数据 , 读取模式下 Data = None
    if Act == "r":  # 读取模式
        Key = "JSON"  # 把Key转化为字符串
        for i in Keys:  # 循化Keys列表
            Key = Key + "['%s']" % i  # 把循环字符拼接到Key变量
        Result = eval(Key)  # 执行字符串表达式并返值
        return Result
    elif Act == "u":  # 更新模式
        Key = "JSON"
        for i in Keys:
            Key = Key + "['%s']" % i
        Key = Key + "='%s'" % Data  # 把需要修改的内容拼接到Key变量
        exec(Key)  # 执行字符串代码
        Result = JSON
        return Result


# 读取模式
print(JSON_Msg_Read_and_Update("r",dic,["a1","a2","a3","a4","a5",],None,))

# 读取模式
print(JSON_Msg_Read_and_Update("u",dic,["a1","a2","a3","a4","a5","a6",],10,))
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题