【问题标题】:Access nested dictionary items via a list of keys?通过键列表访问嵌套字典项?
【发布时间】:2013-01-19 12:05:27
【问题描述】:

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

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

【问题讨论】:

标签: python list dictionary


【解决方案1】:

我用这个

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

【讨论】:

  • 不鼓励仅使用代码的答案。请提供您的答案如何解决问题的摘要,以及为什么它可能比提供的其他答案更可取。
【解决方案2】:

使用for 循环似乎更符合pythonic。 请参阅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。

【讨论】:

  • 我更喜欢这个解决方案 - 但要小心。如果我没记错的话,因为 Python 字典不是不可变的 getFromDict 有可能破坏调用者的 dataDict。我会先copy.deepcopy(dataDict)。当然,(如所写)在第二个函数中需要这种行为。
  • 这与可变性无关 - 只是将 dataDict 变量名重新分配给新变量(子字典)
  • @DylanF 你能解释一下这会如何破坏输入吗?看起来只是给我重新绑定了一个局部变量名。
  • @wim 我想我的意思是,如果您要提取一个可变对象并开始更改它,那么您也在更改原始字典中的对象。回想起来,我不知道这是否真的是令人惊讶的行为。只是要记住的事情。
  • @DylanF 好的,我现在明白了。不过,这不是getFromDict 本身破坏了调用者的dataDict 吗?它来自改变返回值,这是在函数之外完成的。如果用户不希望这样做,用户可以随时制作副本,但无法撤消在函数内部制作的副本 - 因此不复制更加灵活。
【解决方案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 样式指南 prescribes snake_case names for functions。上面的方法同样适用于列表或字典和列表的混合,所以名称应该是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]]

【讨论】:

  • 嵌套映射集也应该创建不存在的节点,imo:整数键的列表,字符串键的字典。
  • @user1353510:不同的用例需要不同的行为。这里的代码不会创建中介,不会。
  • @user1353510:对于默认值,在当前 return 行周围使用 try:、except (KeyError, IndexError): return default_value。
  • @user1353510:其他用例见List to nested dictionary in python;使用dict.setdefault() 而不是dict.__getitem__。
【解决方案4】:

你可以使用pydash:

import pydash as _

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

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

【讨论】:

    【解决方案5】:

    你可以在python中使用eval函数。

    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 字典。

    【讨论】:

      【解决方案6】:

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

      二传手:

      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
      

      【讨论】:

        【解决方案7】:

        很高兴看到这些答案是因为有两种静态方法来设置和获取嵌套属性。这些解决方案比使用嵌套树要好得多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]
        

        【讨论】:

          【解决方案8】:

          一种连接字符串的方法:

          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
          

          【讨论】:

            【解决方案9】:

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

            dict 是包含我们值的字典

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

            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
            

            【讨论】:

              【解决方案10】:

              检查然后设置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()
              

              【讨论】:

                【解决方案11】:

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

                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,这可以用作在您的主代码中检查以执行替代任务。

                【讨论】:

                  【解决方案12】:

                  如果您还希望能够使用任意 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
                  

                  【讨论】:

                    【解决方案13】:

                    用递归解决了这个问题:

                    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
                    

                    【讨论】:

                    • 非常好,我添加了一个额外的 if 条件来处理丢失的键:def get(d,l, default_val=None): if l[0] not in d: return default_val elif len(l)==1: return d[l[0]] else: return get(d[l[0]],l[1:])
                    【解决方案14】:

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

                    【讨论】:

                      【解决方案15】:

                      使用递归函数怎么样?

                      获取值:

                      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
                      

                      【讨论】:

                        【解决方案16】:

                        与其每次要查找一个值时都会对性能造成影响,不如将字典展平一次,然后像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'] 查找项目,这将为您提供1。

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

                        【讨论】:

                          【解决方案17】:

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

                          (Setting a value in a nested python dictionary given a list of indices and value) 中的 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)
                          

                          【讨论】:

                          • 为什么在nested_set中任意限制'keys'参数长度为2或更多?
                          【解决方案18】:

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

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

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

                          【讨论】:

                            猜你喜欢
                            • 2016-01-07
                            • 1970-01-01
                            • 2019-04-11
                            • 2011-05-13
                            • 2016-11-19
                            • 1970-01-01
                            • 1970-01-01
                            相关资源
                            最近更新 更多