Python 通过键列表访问嵌套的字典项?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14692690/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 12:09:52  来源:igfitidea点击:

Access nested dictionary items via a list of keys?

pythonlistdictionary

提问by kolergy

I have a complex dictionary structure which I would like to access via a list of keys to address the correct item.

我有一个复杂的字典结构,我想通过键列表访问它来寻址正确的项目。

dataDict = {
    "a":{
        "r": 1,
        "s": 2,
        "t": 3
        },
    "b":{
        "u": 1,
        "v": {
            "x": 1,
            "y": 2,
            "z": 3
        },
        "w": 3
        }
}    

maplist = ["a", "r"]

or

或者

maplist = ["b", "v", "y"]

I have made the following code which works but I'm sure there is a better and more efficient way to do this if anyone has an idea.

我已经制作了以下代码,但如果有人有想法,我相信有更好、更有效的方法来做到这一点。

# 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

采纳答案by Martijn Pieters

Use reduce()to traverse the dictionary:

使用reduce()遍历词典:

from functools import reduce  # forward compatibility for Python 3
import operator

def getFromDict(dataDict, mapList):
    return reduce(operator.getitem, mapList, dataDict)

and reuse getFromDictto find the location to store the value for setInDict():

并重getFromDict用以查找存储值的位置setInDict()

def setInDict(dataDict, mapList, value):
    getFromDict(dataDict, mapList[:-1])[mapList[-1]] = value

All but the last element in mapListis needed to find the 'parent' dictionary to add the value to, then use the last element to set the value to the right key.

除了最后一个元素之外的所有元素mapList都需要找到要添加值的“父”字典,然后使用最后一个元素将值设置为正确的键。

Demo:

演示:

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

Note that the Python PEP8 style guide prescribes snake_case names for functions. The above works equally well for lists or a mix of dictionaries and lists, so the names should really be get_by_path()and set_by_path():

请注意,Python PEP8 风格指南规定了函数的蛇形名称。以上对于列表或字典和列表的混合同样适用,因此名称应该是get_by_path()and 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

回答by dmmfll

This library may be helpful: https://github.com/akesterson/dpath-python

这个库可能会有所帮助:https: //github.com/akesterson/dpath-python

A python library for accessing and searching dictionaries via /slashed/paths ala xpath

Basically it lets you glob over a dictionary as if it were a filesystem.

用于通过 /slashed/paths ala xpath 访问和搜索字典的 python 库

基本上它可以让你遍历字典,就好像它是一个文件系统。

回答by eafit

Using reduce is clever, but the OP's set method may have issues if the parent keys do not pre-exist in the nested dictionary. Since this is the first SO post I saw for this subject in my google search, I would like to make it slightly better.

使用 reduce 很聪明,但是如果父键不预先存在于嵌套字典中,则 OP 的 set 方法可能会出现问题。由于这是我在谷歌搜索中看到的关于这个主题的第一篇 SO 帖子,我想让它稍微好一点。

The set method in ( Setting a value in a nested python dictionary given a list of indices and value) seems more robust to missing parental keys. To copy it over:

中的 set 方法(在给定索引和值列表的情况下在嵌套 python 字典中设置值)似乎对缺少父键更健壮。复制它:

def nested_set(dic, keys, value):
    for key in keys[:-1]:
        dic = dic.setdefault(key, {})
    dic[keys[-1]] = value

Also, it can be convenient to have a method that traverses the key tree and get all the absolute key paths, for which I have created:

此外,有一种方法可以方便地遍历密钥树并获取我创建的所有绝对密钥路径:

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()], [])

One use of it is to convert the nested tree to a pandas DataFrame, using the following code (assuming that all leafs in the nested dictionary have the same depth).

它的一种用途是使用以下代码将嵌套树转换为 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)

回答by DomTomCat

  1. The accepted solution won't work directly for python3 - it will need an from functools import reduce.
  2. Also it seems more pythonic to use a forloop. See the quote from What's New In Python 3.0.

    Removed reduce(). Use functools.reduce()if you really need it; however, 99 percent of the time an explicit forloop is more readable.

  3. Next, the accepted solution doesn't set non-existing nested keys (it returns a KeyError) - see @eafit's answer for a solution
  1. 接受的解决方案不能直接用于 python3 - 它需要一个from functools import reduce.
  2. 此外,使用for循环似乎更像是 Pythonic 。请参阅Python 3.0 中的新增功能中的引用。

    已删除reduce()functools.reduce()如果您确实需要,请使用它;然而,在 99% 的情况下,显式for循环更具可读性。

  3. 接下来,接受的解决方案不会设置不存在的嵌套键(它返回 a KeyError)-有关解决方案,请参阅@eafit 的答案

So why not use the suggested method from kolergy's question for getting a value:

那么为什么不使用 kolergy 问题中建议的方法来获取值:

def getFromDict(dataDict, mapList):    
    for k in mapList: dataDict = dataDict[k]
    return dataDict

And the code from @eafit's answer for setting a value:

来自@eafit 的用于设置值的答案的代码:

def nested_set(dic, keys, value):
    for key in keys[:-1]:
        dic = dic.setdefault(key, {})
    dic[keys[-1]] = value

Both work straight in python 2 and 3

两者都直接在 python 2 和 3 中工作

回答by OkezieE

Instead of taking a performance hit each time you want to look up a value, how about you flatten the dictionary once then simply look up the key like b:v:y

与其在每次要查找值时都对性能造成影响,不如将字典展平一次,然后简单地查找键,例如 b:v:y

def flatten(mydict):
  new_dict = {}
  for key,value in mydict.items():
    if type(value) == dict:
      _dict = {':'.join([key, _key]):_value for _key, _value in flatten(value).items()}
      new_dict.update(_dict)
    else:
      new_dict[key]=value
  return new_dict

dataDict = {
"a":{
    "r": 1,
    "s": 2,
    "t": 3
    },
"b":{
    "u": 1,
    "v": {
        "x": 1,
        "y": 2,
        "z": 3
    },
    "w": 3
    }
}    

flat_dict = flatten(dataDict)
print flat_dict
{'b:w': 3, 'b:u': 1, 'b:v:y': 2, 'b:v:x': 1, 'b:v:z': 3, 'a:r': 1, 'a:s': 2, 'a:t': 3}

This way you can simply look up items using flat_dict['b:v:y']which will give you 1.

这样你就可以简单地使用flat_dict['b:v:y']它来查找项目,这会给你1

And instead of traversing the dictionary on each lookup, you may be able to speed this up by flattening the dictionary and saving the output so that a lookup from cold start would mean loading up the flattened dictionary and simply performing a key/value lookup with no traversal.

而不是在每次查找时遍历字典,您可以通过展平字典并保存输出来加快速度,这样从冷启动开始查找就意味着加载展平的字典并简单地执行键/值查找而不用遍历。

回答by xyres

How about using recursive functions?

如何使用递归函数?

To get a value:

获取值:

def getFromDict(dataDict, maplist):
    first, rest = maplist[0], maplist[1:]

    if rest: 
        # if `rest` is not empty, run the function recursively
        return getFromDict(dataDict[first], rest)
    else:
        return dataDict[first]

And to set a value:

并设置一个值:

def setInDict(dataDict, maplist, value):
    first, rest = maplist[0], maplist[1:]

    if rest:
        try:
            if not isinstance(dataDict[first], dict):
                # if the key is not a dict, then make it a dict
                dataDict[first] = {}
        except KeyError:
            # if key doesn't exist, create one
            dataDict[first] = {}

        setInDict(dataDict[first], rest, value)
    else:
        dataDict[first] = value

回答by Arount

Pure Python style, without any import:

纯 Python 风格,没有任何导入:

def nested_set(element, value, *keys):
    if type(element) is not dict:
        raise AttributeError('nested_set() expects dict as first argument.')
    if len(keys) < 2:
        raise AttributeError('nested_set() expects at least three arguments, not enough given.')

    _keys = keys[:-1]
    _element = element
    for key in _keys:
        _element = _element[key]
    _element[keys[-1]] = value

example = {"foo": { "bar": { "baz": "ok" } } }
keys = ['foo', 'bar']
nested_set(example, "yay", *keys)
print(example)

Output

输出

{'foo': {'bar': 'yay'}}

回答by Poh Zi How

Solved this with recursion:

用递归解决了这个问题:

def get(d,l):
    if len(l)==1: return d[l[0]]
    return get(d[l[0]],l[1:])

Using your example:

使用您的示例:

dataDict = {
    "a":{
        "r": 1,
        "s": 2,
        "t": 3
        },
    "b":{
        "u": 1,
        "v": {
            "x": 1,
            "y": 2,
            "z": 3
        },
        "w": 3
        }
}
maplist1 = ["a", "r"]
maplist2 = ["b", "v", "y"]
print(get(dataDict, maplist1)) # 1
print(get(dataDict, maplist2)) # 2

回答by Grant Palmer

If you also want the ability to work with arbitrary json including nested lists and dicts, and nicely handle invalid lookup paths, here's my solution:

如果您还希望能够使用包括嵌套列表和字典在内的任意 json,并很好地处理无效的查找路径,这是我的解决方案:

from functools import reduce


def get_furthest(s, path):
    '''
    Gets the furthest value along a given key path in a subscriptable structure.

    subscriptable, list -> any
    :param s: the subscriptable structure to examine
    :param path: the lookup path to follow
    :return: a tuple of the value at the furthest valid key, and whether the full path is valid
    '''

    def step_key(acc, key):
        s = acc[0]
        if isinstance(s, str):
            return (s, False)
        try:
            return (s[key], acc[1])
        except LookupError:
            return (s, False)

    return reduce(step_key, path, (s, True))


def get_val(s, path):
    val, successful = get_furthest(s, path)
    if successful:
        return val
    else:
        raise LookupError('Invalid lookup path: {}'.format(path))


def set_val(s, path, value):
    get_val(s, path[:-1])[path[-1]] = value

回答by Pulkit

An alternative way if you don't want to raise errors if one of the keys is absent (so that your main code can run without interruption):

如果您不想在缺少其中一个键的情况下引发错误(以便您的主代码可以不间断地运行),则另一种方法是:

def get_value(self,your_dict,*keys):
    curr_dict_ = your_dict
    for k in keys:
        v = curr_dict.get(k,None)
        if v is None:
            break
        if isinstance(v,dict):
            curr_dict = v
    return v

In this case, if any of the input keys is not present, None is returned, which can be used as a check in your main code to perform an alternative task.

在这种情况下,如果任何输入键不存在,则返回 None ,它可以用作主代码中的检查以执行替代任务。