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

Python 在嵌套字典和列表中查找所有出现的键,python,recursion,dictionary,traversal,Python,Recursion,Dictionary,Traversal,我有一本这样的字典: { "id" : "abcde", "key1" : "blah", "key2" : "blah blah", "nestedlist" : [ { "id" : "qwerty", "nestednestedlist" : [ { "id" : "xyz", "keyA" : "blah blah blah" }, { "id" : "fghi", "keyZ"

我有一本这样的字典:

{ "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" }] } ] } 
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]]
}
基本上是一个具有任意深度的嵌套列表、字典和字符串的字典

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

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

["abcde", "qwerty", "xyz", "fghi", "asdf", "yuiop"]
秩序并不重要

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

编辑:@Anthon注意到这对直接嵌套的列表不起作用。如果您的输入中有此项,则可以使用此项:

def find(key, value):
  for k, v in (value.iteritems() 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

但是我认为原始版本更容易理解,所以我将保留它。

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

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')))
def递归获取(搜索指令,字段):
“”“获取具有嵌套列表和dict的dict,
并在所有DICT中搜索字段的键
假如
"""
字段_found=[]
对于键,搜索中的值\u dict.iteritems():
如果键==字段:
找到字段\追加(值)
elif isinstance(值,dict):
结果=递归获取(值、字段)
对于结果中的结果:
找到字段。\u追加(结果)
elif isinstance(值、列表):
对于有价值的项目:
如果存在(项目,指令):
更多结果=递归获取(项、字段)
对于另一个导致更多结果的结果:
字段\已找到。追加(另一个\结果)
返回未找到的字段

< /代码> 另一个变体,其中包括找到结果的嵌套路径(<强>注释:该版本不考虑列表):

以下是我的尝试:

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

>>findMe={'Me':{'a':2,'Me':'bop'},'z':{'Me':4}
>>>钥匙孔(“我”,findMe)
>>>[x代表钥匙孔中的x('Me',findMe)]
[bop',4]

我发现这个Q/A非常有趣,因为它为同一个问题提供了几种不同的解决方案。我获取了所有这些函数,并用一个复杂的dictionary对象对它们进行了测试。我不得不从测试中删除两个函数,因为它们有很多失败的结果,并且它们不支持将列表或dict作为值返回,我认为这很重要,因为函数应该为几乎所有的数据做好准备

因此,我通过
timeit
模块在100000次迭代中抽取了其他函数,结果如下:

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)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
所有函数都有相同的搜索指针(“日志”)和相同的字典对象,其构造如下:

{ "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" }] } ] } 
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\u dict\u 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
之外的其他项目彼此非常接近。只有在查找字符串时,
fun
keyHole
函数才起作用


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

我只是想用
中获得的收益和接受顶级列表来重复@hexerei软件的优秀答案

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的评论,如果您想迭代一个键列表/键集:

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]}的列表,而不是字符串键。

pip安装嵌套查找
完全符合您的要求:

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

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

我无法在这里发布解决方案,因此我想写一些更灵活的东西

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

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\u term\b'

一样。我唯一要改变的是
对于d中的k
对于k,d中的value.items()
,随后使用
value
而不是
d[k]谢谢,这很好。需要很小的修改,因为我的列表可以包含字符串和DITS(我没有提到),但是其他的都是完美的。这适合一个非常狭窄的情况,你应该考虑从“HFEFEI软件”的答案,叫做“代码> GeNEngRealSudioStudio< /Cord>我得到了错误。“TypeError:类型为'NoneType'的参数不可编辑”此解决方案似乎不支持列表此方法也很有效,但如果遇到直接包含字符串的列表(我忘了在示例中包含该字符串),同样会遇到问题.我想在最后两行之前加入一个
isinstance
检查一个
dict
。谢谢你的表扬,但我会为我的代码的干净而不是速度感到骄傲。95%的时间,是的。剩下的(很少)在某些情况下,时间限制可能会迫使我选择更快的版本,而不是更干净的版本。但我不喜欢这样。这总是意味着我的继任者要承担大量的工作,他们将不得不维护这些代码。这是一个风险,因为我的继任者可能会感到困惑。那时我将不得不写很多评论,可能是一个完整的评论文件解释我的动机,计时实验,他们的结果等。这对我和所有同事来说是一个更好的方法,让它正确地完成。更干净是一个方法
document = [ { 'taco' : 42 } , { 'salsa' : [ { 'burrito' : { 'taco' : 69 } } ] } ]

>>> print(nested_lookup('taco', document))
[42, 69]
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