使用点符号字符串“a.b.c.d.e”检查嵌套字典,自动创建缺失的级别

Posted

技术标签:

【中文标题】使用点符号字符串“a.b.c.d.e”检查嵌套字典,自动创建缺失的级别【英文标题】:Checking a nested dictionary using a dot notation string "a.b.c.d.e", automatically create missing levels 【发布时间】:2012-09-07 01:06:35 【问题描述】:

给定以下字典:

d = "a":"b":"c":"winning!"

我有这个字符串(来自外部来源,我无法改变这个比喻)。

k = "a.b.c"

我需要确定字典是否有键'c',如果没有的话我可以添加。

这可以很好地检索点符号值:

reduce(dict.get, key.split("."), d)

但我不知道如何“减少”has_key 支票或类似的东西。

我的最终问题是:给定"a.b.c.d.e",我需要在字典中创建所有必要的元素,但如果它们已经存在,则不要踩它们。

【问题讨论】:

是的,只需接受您自己对问题的回答即可。这完全没问题(甚至是需要的,所以有相同问题的人会看到存在答案)。 源代码实际上是 Cocoa KVC 吗?而且,如果是这样,您可以只使用 PyObjC 而不是纯 Python 吗? (除了更简单之外,这还可以保证键的含义与它们的意图完全相同,即使其他人的代码中存在错误......) 使用点符号“作为字符串”搜索字典有什么特别之处?必须为此使用一些附加功能。不带任何字符串x = conf.a.b.c 搜索多维字典 JS 样式怎么样。我不知道在 Python 中是否可行。但是使用字符串并不酷 那个“减少”片段帮助了我。谢谢! 几乎重复,虽然从 2018 年开始,大概你问的是 Python 2.x 而不是 3.x Set Python dict items recursively, when given a compound key 'foo.bar.baz' 【参考方案1】:

您可以使用无限嵌套的defaultdict:

>>> from collections import defaultdict
>>> infinitedict = lambda: defaultdict(infinitedict)
>>> d = infinitedict()
>>> d['key1']['key2']['key3']['key4']['key5'] = 'test'
>>> d['key1']['key2']['key3']['key4']['key5']
'test'

鉴于您的虚线字符串,您可以执行以下操作:

>>> import operator
>>> keys = "a.b.c".split(".")
>>> lastplace = reduce(operator.getitem, keys[:-1], d)
>>> lastplace.has_key(keys[-1])
False

你可以设置一个值:

>>> lastplace[keys[-1]] = "something"
>>> reduce(operator.getitem, keys, d)
'something'
>>> d['a']['b']['c']
'something'

【讨论】:

这很深。 infinitedict = lambda: defaultdict(infinitedict) +1【参考方案2】:

...或使用递归:

def put(d, keys, item):
    if "." in keys:
        key, rest = keys.split(".", 1)
        if key not in d:
            d[key] = 
        put(d[key], rest, item)
    else:
        d[keys] = item

def get(d, keys):
    if "." in keys:
        key, rest = keys.split(".", 1)
        return get(d[key], rest)
    else:
        return d[keys]

【讨论】:

这可以通过只调用一次 split 并将结果列表传递给递归函数来改进。 这个几乎有。问题是,put 不考虑键是否存在,而是需要用 dict 覆盖的文本值。 我在修改后使用了这个。与@l4mpi 的回答非常相似。【参考方案3】:

迭代方法怎么样?

def create_keys(d, keys):
    for k in keys.split("."):
        if not k in d: d[k] =   #if the key isn't there yet add it to d
        d = d[k]                  #go one level down and repeat

如果您需要将最后一个键值映射到字典以外的任何其他内容,您可以将该值作为附加参数传递并在循环后设置:

def create_keys(d, keys, value):
    keys = keys.split(".")
    for k in keys[:-1]:
        if not k in d: d[k] = 
        d = d[k]            
    d[keys[-1]] = value

【讨论】:

【参考方案4】:
d = "a":
k = "a.b.c".split(".")

def f(d, i):
    if i >= len(k):
        return "winning!"
    c = k[i]
    d[c] = f(d.get(c, ), i + 1)
    return d

print f(d, 0)
"'a': 'b': 'c': 'winning!'"

【讨论】:

我为你的同事不得不维护这种代码感到抱歉。 dkfic 是什么意思? d、k 和 c 根据问题。 f 是函数,i 是索引【参考方案5】:

我认为这个讨论非常有用,但为了我的目的只是获取一个值(而不是设置它),我在不存在密钥时遇到了问题。所以,为了给选项增加我的天赋,你可以使用reduce 结合调整后的dict.get() 来适应密钥存在的场景,然后返回 None:

from functools import reduce
import re
from typing import Any, Optional

def find_key(dot_notation_path: str, payload: dict) -> Any:
    """Try to get a deep value from a dict based on a dot-notation"""

    def get_despite_none(payload: Optional[dict], key: str) -> Any:
        """Try to get value from dict, even if dict is None"""
        if not payload or not isinstance(payload, (dict, list)):
            return None
        # can also access lists if needed, e.g., if key is '[1]'
        if (num_key := re.match(r"^\[(\d+)\]$", key)) is not None:
            try:
                return payload[int(num_key.group(1))]
            except IndexError:
                return None
        else:
            return payload.get(key, None)

    found = reduce(get_despite_none, dot_notation_path.split("."), payload)
   
    # compare to None, as the key could exist and be empty
    if found is None:
        raise KeyError()
    return found

在我的用例中,我需要在 HTTP 请求负载中找到一个键,该负载通常也可以包含列表。以下示例有效:

payload = 
    "haystack1": 
        "haystack2": 
            "haystack3": None, 
            "haystack4": "needle"
        
    ,
    "haystack5": [
        "haystack6": None, 
        "haystack7": "needle"
    ],
    "haystack8": ,


find_key("haystack1.haystack2.haystack4", payload)
# "needle"
find_key("haystack5.[1].haystack7", payload)
# "needle"
find_key("[0].haystack5.[1].haystack7", [payload, None])
# "needle"
find_key("haystack8", payload)
# 
find_key("haystack1.haystack2.haystack4.haystack99", payload)
# KeyError

编辑:添加列表访问器

【讨论】:

以上是关于使用点符号字符串“a.b.c.d.e”检查嵌套字典,自动创建缺失的级别的主要内容,如果未能解决你的问题,请参考以下文章

递归替换字典中的字符

如何按值和键的多个条件对嵌套字典进行排序?

有没有办法使用连接的字符串键访问嵌套字典? [复制]

如何从 Python Pandas Dataframe 中的 STRING 列中提取嵌套字典?

Python:带有字符串列表和子字典的嵌套字典

Fastlane增加内部版本号会抛出格式错误的64位a.b.c.d.e版本号