176

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

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
4

18 回答 18

295

用于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()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

为了完成,删除键的功能:

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]]
于 2013-02-04T18:07:38.353 回答
63

使用循环似乎更pythonic for请参阅What's New In Python 3.0中的引用。

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

def nested_get(dic, keys):    
    for key in keys:
        dic = dic[key]
    return dic

请注意,接受的解决方案不会设置不存在的嵌套键(它会引发KeyError)。使用以下方法将创建不存在的节点:

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

该代码适用于 Python 2 和 3。

于 2016-06-08T13:47:04.693 回答
16

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

(在给定索引和值列表的情况下设置嵌套 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)
于 2016-04-29T04:38:54.187 回答
11

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

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

基本上它可以让你在字典上查找,就好像它是一个文件系统一样。

于 2015-04-29T06:02:00.523 回答
2

用递归解决了这个问题:

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

使用您的示例:

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
于 2018-03-05T13:38:11.057 回答
2

与其每次要查找值时都会受到性能影响,不如将字典展平一次然后简单地查找键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}

通过这种方式,您可以简单地查找使用flat_dict['b:v:y']which will give you的项目1

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

于 2017-03-01T02:07:58.350 回答
2

使用递归函数怎么样?

要获得一个值:

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]

并设置一个值:

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
于 2017-12-08T22:56:27.637 回答
1

很高兴看到这些答案有两种静态方法来设置和获取嵌套属性。这些解决方案比使用嵌套树好得多https://gist.github.com/hrldcpr/2012250

这是我的实现。

用法

设置嵌套属性调用sattr(my_dict, 1, 2, 3, 5) is equal to my_dict[1][2][3][4]=5

获取嵌套属性调用gattr(my_dict, 1, 2)

def gattr(d, *attrs):
    """
    This method receives a dict and list of attributes to return the innermost value of the give dict       
    """
    try:
        for at in attrs:
            d = d[at]
        return d
    except(KeyError, TypeError):
        return None


def sattr(d, *attrs):
    """
    Adds "val" to dict in the hierarchy mentioned via *attrs
    For ex:
    sattr(animals, "cat", "leg","fingers", 4) is equivalent to animals["cat"]["leg"]["fingers"]=4
    This method creates necessary objects until it reaches the final depth
    This behaviour is also known as autovivification and plenty of implementation are around
    This implementation addresses the corner case of replacing existing primitives
    https://gist.github.com/hrldcpr/2012250#gistcomment-1779319
    """
    for attr in attrs[:-2]:
        if type(d.get(attr)) is not dict:
            d[attr] = {}
        d = d[attr]
    d[attrs[-2]] = attrs[-1]
于 2018-12-03T00:56:24.193 回答
1

如果您不想在其中一个键不存在时引发错误,另一种方法是(这样您的主代码可以不间断地运行):

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

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

于 2018-03-16T01:30:53.967 回答
1

纯 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)

输出

{'foo': {'bar': 'yay'}}
于 2018-02-16T14:00:54.440 回答
0

如何检查然后设置 dict 元素而不处理所有索引两次?

解决方案:

def nested_yield(nested, keys_list):
    """
    Get current nested data by send(None) method. Allows change it to Value by calling send(Value) next time
    :param nested: list or dict of lists or dicts
    :param keys_list: list of indexes/keys
    """
    if not len(keys_list):  # assign to 1st level list
        if isinstance(nested, list):
            while True:
                nested[:] = yield nested
        else:
            raise IndexError('Only lists can take element without key')


    last_key = keys_list.pop()
    for key in keys_list:
        nested = nested[key]

    while True:
        try:
            nested[last_key] = yield nested[last_key]
        except IndexError as e:
            print('no index {} in {}'.format(last_key, nested))
            yield None

示例工作流程:

ny = nested_yield(nested_dict, nested_address)
data_element = ny.send(None)
if data_element:
    # process element
    ...
else:
    # extend/update nested data
    ny.send(new_data_element)
    ...
ny.close()

测试

>>> cfg= {'Options': [[1,[0]],[2,[4,[8,16]]],[3,[9]]]}
    ny = nested_yield(cfg, ['Options',1,1,1])
    ny.send(None)
[8, 16]
>>> ny.send('Hello!')
'Hello!'
>>> cfg
{'Options': [[1, [0]], [2, [4, 'Hello!']], [3, [9]]]}
>>> ny.close()
于 2018-08-15T09:46:38.133 回答
0

聚会很晚,但张贴以防万一这可能对将来的某人有所帮助。对于我的用例,以下功能效果最好。用于从字典中提取任何数据类型

dict是包含我们的值的字典

list是实现我们价值的“步骤”列表

def getnestedvalue(dict, list):

    length = len(list)
    try:
        for depth, key in enumerate(list):
            if depth == length - 1:
                output = dict[key]
                return output
            dict = dict[key]
    except (KeyError, TypeError):
        return None

    return None
于 2018-12-09T08:43:06.723 回答
0

如果您还希望能够使用包括嵌套列表和字典在内的任意 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
于 2018-03-06T21:30:01.930 回答
0

我用这个

def get_dictionary_value(dictionary_temp, variable_dictionary_keys):
     try:
          if(len(variable_dictionary_keys) == 0):
               return str(dictionary_temp)

          variable_dictionary_key = variable_dictionary_keys[0]
          variable_dictionary_keys.remove(variable_dictionary_key)

          return get_dictionary_value(dictionary_temp[variable_dictionary_key] , variable_dictionary_keys)

     except Exception as variable_exception:
          logging.error(variable_exception)
 
          return ''

于 2021-04-20T17:08:36.613 回答
0

您可以使用 pydash:

import pydash as _

_.get(dataDict, ["b", "v", "y"], default='Default')

https://pydash.readthedocs.io/en/latest/api.html

于 2020-05-20T19:00:32.307 回答
-1

一种连接字符串的方法:

def get_sub_object_from_path(dict_name, map_list):
    for i in map_list:
        _string = "['%s']" % i
        dict_name += _string
    value = eval(dict_name)
    return value
#Sample:
_dict = {'new': 'person', 'time': {'for': 'one'}}
map_list = ['time', 'for']
print get_sub_object_from_path("_dict",map_list)
#Output:
#one
于 2018-12-29T05:10:48.810 回答
-1

扩展@DomTomCat 和其他人的方法,这些功能(即,通过 deepcopy 返回修改后的数据而不影响输入)设置器和映射器适用于嵌套dictlist.

二传手:

def set_at_path(data0, keys, value):
    data = deepcopy(data0)
    if len(keys)>1:
        if isinstance(data,dict):
            return {k:(set_by_path(v,keys[1:],value) if k==keys[0] else v) for k,v in data.items()}
        if isinstance(data,list):
            return [set_by_path(x[1],keys[1:],value) if x[0]==keys[0] else x[1] for x in enumerate(data)]
    else:
        data[keys[-1]]=value
        return data

映射器:

def map_at_path(data0, keys, f):
    data = deepcopy(data0)
    if len(keys)>1:
        if isinstance(data,dict):
            return {k:(map_at_path(v,keys[1:],f) if k==keys[0] else v) for k,v in data.items()}
        if isinstance(data,list):
            return [map_at_path(x[1],keys[1:],f) if x[0]==keys[0] else x[1] for x in enumerate(data)]
    else:
        data[keys[-1]]=f(data[keys[-1]])
        return data
于 2019-05-01T19:00:07.467 回答
-4

您可以eval在 python 中使用该函数。

def nested_parse(nest, map_list):
    nestq = "nest['" + "']['".join(map_list) + "']"
    return eval(nestq, {'__builtins__':None}, {'nest':nest})

解释

对于您的示例查询:maplist = ["b", "v", "y"]

nestq"nest['b']['v']['y']"nest嵌套字典的位置。

eval内置函数执行给定的字符串。但是,重要的是要小心使用eval函数可能产生的漏洞。讨论可以在这里找到:

  1. https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html
  2. https://www.journaldev.com/22504/python-eval-function

nested_parse()函数中,我确保没有__builtins__可用的全局变量,只有可用的局部变量是nest字典。

于 2020-02-05T07:28:11.487 回答