使用点符号字符串“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!'"
【讨论】:
我为你的同事不得不维护这种代码感到抱歉。d
、k
、f
、i
和 c
是什么意思?
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”检查嵌套字典,自动创建缺失的级别的主要内容,如果未能解决你的问题,请参考以下文章