在嵌套字典和列表中查找所有出现的键

Posted

技术标签:

【中文标题】在嵌套字典和列表中查找所有出现的键【英文标题】:Find all occurrences of a key in nested dictionaries and lists 【发布时间】:2012-04-06 03:35:41 【问题描述】:

我有一本这样的字典:

 "id" : "abcde",
  "key1" : "blah",
  "key2" : "blah blah",
  "nestedlist" : [ 
     "id" : "qwerty",
      "nestednestedlist" : [ 
         "id" : "xyz",
          "keyA" : "blah blah blah" ,
         "id" : "fghi",
          "keyZ" : "blah blah blah" ],
      "anothernestednestedlist" : [ 
         "id" : "asdf",
          "keyQ" : "blah blah" ,
         "id" : "yuiop",
          "keyW" : "blah" ]  ]  

基本上是一个包含任意深度的嵌套列表、字典和字符串的字典。

遍历它以提取每个“id”键的值的最佳方法是什么?我想实现类似“//id”的 XPath 查询。 “id”的值始终是一个字符串。

所以从我的例子来看,我需要的输出基本上是:

["abcde", "qwerty", "xyz", "fghi", "asdf", "yuiop"]

顺序并不重要。

【问题讨论】:

另见: ***.com/questions/7681301/… ***.com/a/16508328/42223 如果我们将None 作为输入,您的大多数解决方案都会崩溃。你关心健壮性吗? (因为这现在被用作规范问题) 【参考方案1】:
d =  "id" : "abcde",
    "key1" : "blah",
    "key2" : "blah blah",
    "nestedlist" : [ 
     "id" : "qwerty",
        "nestednestedlist" : [ 
         "id" : "xyz", "keyA" : "blah blah blah" ,
         "id" : "fghi", "keyZ" : "blah blah blah" ],
        "anothernestednestedlist" : [ 
         "id" : "asdf", "keyQ" : "blah blah" ,
         "id" : "yuiop", "keyW" : "blah" ]  ]  


def fun(d):
    if 'id' in d:
        yield d['id']
    for k in d:
        if isinstance(d[k], list):
            for i in d[k]:
                for j in fun(i):
                    yield j

>>> list(fun(d))
['abcde', 'qwerty', 'xyz', 'fghi', 'asdf', 'yuiop']

【讨论】:

我唯一要更改的是将for k in d 更改为for k,value in d.items(),随后使用value 而不是d[k] 谢谢,这很好用。需要非常轻微的修改,因为我的列表可以包含字符串和 dicts(我没有提到),但其他方面都很完美。 这适用于一个非常狭窄的案例,您应该自己考虑来自名为gen_dict_extract的“hexerei软件”的答案 我收到错误“TypeError:'NoneType' 类型的参数不可迭代” 此解决方案似乎不支持列表【参考方案2】:
def find(key, value):
  for k, v in value.items():
    if k == key:
      yield v
    elif isinstance(v, dict):
      for result in find(key, v):
        yield result
    elif isinstance(v, list):
      for d in v:
        for result in find(key, d):
          yield result

编辑:@Anthon 注意到这不适用于直接嵌套的列表。如果你的输入中有这个,你可以使用这个:

def find(key, value):
  for k, v in (value.items() if isinstance(value, dict) else
               enumerate(value) if isinstance(value, list) else []):
    if k == key:
      yield v
    elif isinstance(v, (dict, list)):
      for result in find(key, v):
        yield result

不过我觉得原版比较容易理解,所以就不说了。

【讨论】:

这也很好用,但如果遇到直接包含字符串的列表(我忘记在我的示例中包含),也会遇到问题。我认为在最后两行解决此问题之前添加isinstance 检查dict 感谢您的赞誉,但我会因为代码的简洁而不是速度而感到自豪。 95% 的时间,是的。剩下的(罕见的)场合是一些时间限制可能会迫使我选择更快的版本而不是更干净的版本。但我不喜欢这个。这总是意味着将大量工作交给我的继任者,他必须维护该代码。这是一种风险,因为我的继任者可能会感到困惑。那时我将不得不写很多 cmets,也许是一个完整的文档来解释我的动机、时间实验、他们的结果等。这对我和所有同事来说都是更多的工作才能正确完成。清洁器更简单。 @Alfe - 感谢您的回答。我需要为 Elasticsearch 的特定用例提取嵌套字典中所有出现的字符串,并且此代码在稍作修改后很有用 - ***.com/questions/40586020/… 这完全打破直接包含在列表中的列表。【参考方案3】:
d =  "id" : "abcde",
    "key1" : "blah",
    "key2" : "blah blah",
    "nestedlist" : [
     "id" : "qwerty",
        "nestednestedlist" : [
         "id" : "xyz", "keyA" : "blah blah blah" ,
         "id" : "fghi", "keyZ" : "blah blah blah" ],
        "anothernestednestedlist" : [
         "id" : "asdf", "keyQ" : "blah blah" ,
         "id" : "yuiop", "keyW" : "blah" ]  ] 


def findkeys(node, kv):
    if isinstance(node, list):
        for i in node:
            for x in findkeys(i, kv):
               yield x
    elif isinstance(node, dict):
        if kv in node:
            yield node[kv]
        for j in node.values():
            for x in findkeys(j, kv):
                yield x

print(list(findkeys(d, 'id')))

【讨论】:

这个例子适用于我测试过的每一个复杂的字典。干得好。 这应该是公认的答案,它可以找到嵌套在列表等列表中的字典中的键。 这也适用于 Python3,只要修改了最后的 print 语句。上述解决方案均不适用于 API 响应,其中列表嵌套在列表中的 dicts 等中,但这个效果很好。【参考方案4】:

此函数递归搜索包含嵌套字典和列表的字典。它构建了一个名为 fields_found 的列表,其中包含每次找到该字段时的值。 “字段”是我在字典及其嵌套列表和字典中寻找的键。

def get_recursively(search_dict, field):
    """Takes a dict with nested lists and dicts,
    and searches all dicts for a key of the field
    provided.
    """
    fields_found = []

    for key, value in search_dict.iteritems():

        if key == field:
            fields_found.append(value)

        elif isinstance(value, dict):
            results = get_recursively(value, field)
            for result in results:
                fields_found.append(result)

        elif isinstance(value, list):
            for item in value:
                if isinstance(item, dict):
                    more_results = get_recursively(item, field)
                    for another_result in more_results:
                        fields_found.append(another_result)

    return fields_found

【讨论】:

您可以使用 fields_found.extend(more_results) 而不是运行另一个循环。在我看来会看起来更干净一些。【参考方案5】:

另一种变体,包括找到的结果的嵌套路径(注意:此版本不考虑列表):

def find_all_items(obj, key, keys=None):
    """
    Example of use:
    d = 'a': 1, 'b': 2, 'c': 'a': 3, 'd': 4, 'e': 'a': 9, 'b': 3, 'j': 'c': 4
    for k, v in find_all_items(d, 'a'):
        print "*  =  *".format('->'.join(k), v)    
    """
    ret = []
    if not keys:
        keys = []
    if key in obj:
        out_keys = keys + [key]
        ret.append((out_keys, obj[key]))
    for k, v in obj.items():
        if isinstance(v, dict):
            found_items = find_all_items(v, key, keys=(keys+[k]))
            ret += found_items
    return ret

【讨论】:

【参考方案6】:

这是我的尝试:

def keyHole(k2b,o):
  # print "Checking for %s in "%k2b,o
  if isinstance(o, dict):
    for k, v in o.iteritems():
      if k == k2b and not hasattr(v, '__iter__'): yield v
      else:
        for r in  keyHole(k2b,v): yield r
  elif hasattr(o, '__iter__'):
    for r in [ keyHole(k2b,i) for i in o ]:
      for r2 in r: yield r2
  return

Ex.:

>>> findMe = 'Me':'a':2,'Me':'bop','z':'Me':4
>>> keyHole('Me',findMe)
<generator object keyHole at 0x105eccb90>
>>> [ x for x in keyHole('Me',findMe) ]
['bop', 4]

【讨论】:

【参考方案7】:

我发现这个 Q/A 非常有趣,因为它为同一个问题提供了几种不同的解决方案。我采用了所有这些函数,并用一个复杂的字典对象对它们进行了测试。我不得不从测试中取出两个函数,因为它们必须有很多失败的结果,而且它们不支持将列表或字典作为值返回,我认为这是必不可少的,因为应该为几乎任何准备一个函数> 数据即将到来。

所以我通过timeit 模块在 100.000 次迭代中抽取了其他函数,输出得到以下结果:

0.11 usec/pass on gen_dict_extract(k,o)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6.03 usec/pass on find_all_items(k,o)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
0.15 usec/pass on findkeys(k,o)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1.79 usec/pass on get_recursively(k,o)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
0.14 usec/pass on find(k,o)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
0.36 usec/pass on dict_extract(k,o)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -

所有函数都有相同的搜索针('logging')和相同的字典对象,其构造如下:

o =  'temparature': '50', 
      'logging': 
        'handlers': 
          'console': 
            'formatter': 'simple', 
            'class': 'logging.StreamHandler', 
            'stream': 'ext://sys.stdout', 
            'level': 'DEBUG'
          
        ,
        'loggers': 
          'simpleExample': 
            'handlers': ['console'], 
            'propagate': 'no', 
            'level': 'INFO'
          ,
         'root': 
           'handlers': ['console'], 
           'level': 'DEBUG'
         
       , 
       'version': '1', 
       'formatters': 
         'simple': 
           'datefmt': "'%Y-%m-%d %H:%M:%S'", 
           'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
         
       
     , 
     'treatment': 'second': 5, 'last': 4, 'first': 4,   
     'treatment_plan': [[4, 5, 4], [4, 5, 4], [5, 5, 5]]

所有函数都提供相同的结果,但时间差异很大!函数gen_dict_extract(k,o) 是我从这里的函数改编的函数,实际上它很像Alfe 的find 函数,主要区别在于,我正在检查给定对象是否具有iteritems 函数,以防传递字符串在递归期间:

def gen_dict_extract(key, var):
    if hasattr(var,'iteritems'):
        for k, v in var.iteritems():
            if k == key:
                yield v
            if isinstance(v, dict):
                for result in gen_dict_extract(key, v):
                    yield result
            elif isinstance(v, list):
                for d in v:
                    for result in gen_dict_extract(key, d):
                        yield result

所以这个变体是这里最快和最安全的函数。而find_all_items 非常慢,与第二慢的get_recursivley 相差甚远,而除dict_extract 之外的其余部分彼此接近。 funkeyHole 函数仅在您查找字符串时才有效。

这里有有趣的学习方面:)

【讨论】:

如果你想像我一样搜索多个键,只需:(1)更改为gen_dict_extract(keys, var)(2)将for key in keys:作为第2行并缩进其余部分(3)更改第一个屈服于yield key: v 您将苹果与橙子进行比较。运行返回生成器的函数比运行返回完成结果的函数花费的时间更少。在 next(functionname(k, o) 上尝试 timeit 以获得所有生成器解决方案。 hasattr(var, 'items') for python3 您是否考虑在使用try 的版本中剥离if hasattr 部分以在调用失败时捕获异常(请参阅pastebin.com/ZXvVtV0g 以了解可能的实现)?这将减少属性iteritems 的双重查找(一次用于hasattr(),一次用于调用),因此可能会减少运行时间(这对您来说似乎很重要)。不过没有做任何基准测试。 对于在 Python 3 接管后访问此页面的任何人,请记住 iteritems 已变为 items【参考方案8】:

我只是想使用yield from 迭代@hexerei-software 的出色答案并接受***列表。

def gen_dict_extract(var, key):
    if isinstance(var, dict):
        for k, v in var.items():
            if k == key:
                yield v
            if isinstance(v, (dict, list)):
                yield from gen_dict_extract(v, key)
    elif isinstance(var, list):
        for d in var:
            yield from gen_dict_extract(d, key)

【讨论】:

@hexerei-software 回答的优秀模式:简洁并允许列表列表!我将它与@bruno-bronosky 在他的 cmets 中使用for key in keys 的建议一起使用。我还添加了第二个isinstance(list, tuple) 以获得更多更多 种类。 ;)【参考方案9】:

跟进@hexerei 软件的回答和 @bruno-bronosky 的评论,如果你想遍历一个列表/一组键:

def gen_dict_extract(var, keys):
   for key in keys:
      if hasattr(var, 'items'):
         for k, v in var.items():
            if k == key:
               yield v
            if isinstance(v, dict):
               for result in gen_dict_extract([key], v):
                  yield result
            elif isinstance(v, list):
               for d in v:
                  for result in gen_dict_extract([key], d):
                     yield result    

请注意,我传递的是一个包含单个元素 ([key] 的列表,而不是字符串键。

【讨论】:

【参考方案10】:

pip install nested-lookup 完全符合您的要求:

document = [  'taco' : 42  ,  'salsa' : [  'burrito' :  'taco' : 69   ]  ]

>>> print(nested_lookup('taco', document))
[42, 69]

【讨论】:

与上面的@arainchi 答案相同,此外nested-lookup 库中还有许多其他功能。对于上面的例子,使用from nested_lookup import nested_lookup【参考方案11】:

我无法让此处发布的解决方案开箱即用,因此我想写一些更灵活的东西。

下面的递归函数应该允许您在任意深度的嵌套字典和列表集中收集满足给定键的某种正则表达式模式的所有值。

import re

def search(dictionary, search_pattern, output=None):
    """
    Search nested dictionaries and lists using a regex search
    pattern to match a key and return the corresponding value(s).
    """
    if output is None:
        output = []

    pattern = re.compile(search_pattern)
    
    for k, v in dictionary.items():
        pattern_found = pattern.search(k)
        if not pattern_found:
            if isinstance(v, list):
                for item in v:
                    if isinstance(item, dict):
                        search(item, search_pattern, output)
            if isinstance(v, dict):
                search(v, search_pattern, output)
        else:
            if pattern_found:
                output.append(v)

    return output

如果你想搜索一个特定的词,你总是可以让你的搜索模式类似于r'\bsome_term\b'

【讨论】:

新方法但不适用于嵌套 例如 mylist = 'sad': [[1,1.2],[2,1.3],[3,1.4]] , '小狗': [[2,2.2],[3,2.3]], '快乐':[[3,4.5],[5,6.7,'快乐':[1,2,3]]]

以上是关于在嵌套字典和列表中查找所有出现的键的主要内容,如果未能解决你的问题,请参考以下文章

查找列表中有多少嵌套字典键

使用列表中的项目更改嵌套字典的字典中的值?

python中 列表导入到字典 出现相同的键,如何将值相加

添加键的值并按在 Python 中的字典列表中出现的键对其进行排序

如何重命名字典列表中深度嵌套的键(Python 3)?

在字典中查找重复列表并打印重复列表的键