通过键列表访问嵌套字典项?
我有一个复杂的字典结构,我想通过一系列的键来访问正确的项目。
dataDict = {
"a":{
"r": 1,
"s": 2,
"t": 3
},
"b":{
"u": 1,
"v": {
"x": 1,
"y": 2,
"z": 3
},
"w": 3
}
}
maplist = ["a", "r"]
或者
maplist = ["b", "v", "y"]
我写了以下代码,它可以正常工作,但我相信如果有人有更好的想法,肯定有更好更高效的方法来做到这一点。
# Get a given data from a dictionary with position provided as a list
def getFromDict(dataDict, mapList):
for k in mapList:
dataDict = dataDict[k]
return dataDict
# Set a given data in a dictionary with position provided as a list
def setInDict(dataDict, mapList, value):
for k in mapList[:-1]:
dataDict = dataDict[k]
dataDict[mapList[-1]] = value
22 个回答
使用reduce方法很聪明,但如果父键在嵌套字典中不存在,楼主的set方法可能会有问题。因为这是我在谷歌搜索这个主题时看到的第一个Stack Overflow帖子,所以我想稍微改进一下。
在(给定索引列表和数值设置嵌套Python字典中的值)中的set方法似乎对缺失的父键更稳健。为了方便起见,我把它复制过来:
def nested_set(dic, keys, value):
for key in keys[:-1]:
dic = dic.setdefault(key, {})
dic[keys[-1]] = value
另外,拥有一个可以遍历键树并获取所有绝对键路径的方法也很方便,我为此创建了:
def keysInDict(dataDict, parent=[]):
if not isinstance(dataDict, dict):
return [tuple(parent)]
else:
return reduce(list.__add__,
[keysInDict(v,parent+[k]) for k,v in dataDict.items()], [])
它的一个用途是将嵌套树转换为pandas的DataFrame,使用以下代码(假设嵌套字典中的所有叶子节点深度相同)。
def dict_to_df(dataDict):
ret = []
for k in keysInDict(dataDict):
v = np.array( getFromDict(dataDict, k), )
v = pd.DataFrame(v)
v.columns = pd.MultiIndex.from_product(list(k) + [v.columns])
ret.append(v)
return reduce(pd.DataFrame.join, ret)
使用 for
循环看起来更符合 Python 的风格。你可以看看这段来自 Python 3.0 新特性 的引用。
移除了
reduce()
函数。如果你真的需要它,可以使用functools.reduce()
;不过,99% 的情况下,直接用for
循环会更容易理解。
def nested_get(dic, keys):
for key in keys:
dic = dic[key]
return dic
def nested_set(dic, keys, value):
for key in keys[:-1]:
dic = dic.setdefault(key, {})
dic[keys[-1]] = value
def nested_del(dic, keys):
for key in keys[:-1]:
dic = dic[key]
del dic[keys[-1]]
需要注意的是,接受的解决方案并不会设置不存在的嵌套键(会抛出 KeyError
错误)。而使用上面的方法会创建不存在的节点。
这段代码在 Python 2 和 3 中都能运行。
使用 reduce()
来遍历字典:
from functools import reduce # forward compatibility for Python 3
import operator
def getFromDict(dataDict, mapList):
return reduce(operator.getitem, mapList, dataDict)
然后可以重复使用 getFromDict
来找到存储值的位置,以便使用 setInDict()
:
def setInDict(dataDict, mapList, value):
getFromDict(dataDict, mapList[:-1])[mapList[-1]] = value
在 mapList
中,除了最后一个元素,其他的元素都是用来找到“父”字典,以便把值添加进去,然后用最后一个元素来把值设置到正确的键上。
示例:
>>> getFromDict(dataDict, ["a", "r"])
1
>>> getFromDict(dataDict, ["b", "v", "y"])
2
>>> setInDict(dataDict, ["b", "v", "w"], 4)
>>> import pprint
>>> pprint.pprint(dataDict)
{'a': {'r': 1, 's': 2, 't': 3},
'b': {'u': 1, 'v': {'w': 4, 'x': 1, 'y': 2, 'z': 3}, 'w': 3}}
注意,Python 的 PEP8 风格指南 建议函数使用蛇形命名法。上面的代码同样适用于列表或者字典和列表的混合,所以函数的名字其实应该是 get_by_path()
和 set_by_path()
:
from functools import reduce # forward compatibility for Python 3
import operator
def get_by_path(root, items):
"""Access a nested object in root by item sequence."""
return reduce(operator.getitem, items, root)
def set_by_path(root, items, value):
"""Set a value in a nested object in root by item sequence."""
get_by_path(root, items[:-1])[items[-1]] = value
为了完整性,这里还有一个删除键的函数:
def del_by_path(root, items):
"""Delete a key-value in a nested object in root by item sequence."""
del get_by_path(root, items[:-1])[items[-1]]