14

给定以下字典:

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

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

k = "a.b.c"

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

这适用于检索点符号值:

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

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

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

4

5 回答 5

26

您可以使用无限的嵌套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'
于 2012-09-13T21:16:05.133 回答
15

...或使用递归:

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]
于 2012-09-13T21:49:04.933 回答
4

迭代方法怎么样?

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
于 2012-09-13T21:14:13.693 回答
0
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!'}}}"
于 2012-09-13T22:37:28.503 回答
0

我认为这个讨论非常有用,但是为了我的目的只是获取一个值(而不是设置它),当一个键不存在时我遇到了问题。因此,只是为了将我的天赋添加到选项中,您可以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

编辑:添加列表访问器

于 2021-11-18T14:40:30.060 回答