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


87

我有一本这样的字典:

{ "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"]

顺序并不重要。



如果我们None作为输入通过,您的大多数解决方案都会崩溃。您是否关心稳健性?(因为该问题现在已被用作规范问题)
smci,

Answers:


74

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

因此,我通过该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)是我从此处的函数改编而成的函数,实际上,它find与Alfe的函数非常相似,主要区别在于,如果递归期间传递了字符串,则我要检查给定对象是否具有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
如果您想像我一样搜索多个键,只需:(1)更改为gen_dict_extract(keys, var)(2)放在for key in keys:第2行并缩进(3)将第一个收益更改为yield {key: v}
Bruno Bronosky

6
您正在将苹果与桔子进行比较。运行返回生成器的函数所花的时间少于运行返回完成结果的函数所花的时间。next(functionname(k, o)为所有发电机解决方案尝试timeit on 。
kaleissin

6
hasattr(var, 'items')对于python3
gobrewers14 2013年

1
您是否考虑过在调用失败的情况下剥离if hasattr用于try捕获异常的版本的零件(有关可能的实现,请参见pastebin.com/ZXvVtV0g)?这将减少属性的两倍查找iteritems(一次hasattr()调用一次),从而可能减少运行时间(这对您来说很重要)。不过没有做任何基准测试。
Alfe

2
对于Python 3已经接管而现在访问此页面的任何人,请记住,它iteritems已成为items
Mike Williamson

46
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 dfor k,value in d.items()与后续使用的value替代d[k]
ovgolovin 2012年

谢谢,这很好。由于我的列表可以包含字符串和字典(我没有提到),因此需要进行非常细微的修改,但其他方面都是完美的。
马特·斯温

1
这适合一个非常狭窄的情况,您应该自己考虑一下名为“ hexerei软件”的答案gen_dict_extract
Bruno Bronosky

我收到错误消息“ TypeError:类型'NoneType'的参数不可迭代”
xiaoshir

2
该解决方案似乎并不支持列表不
亚历克斯- [R

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

1
此示例适用于我测试过的每本复杂词典。做得好。

这应该是公认的答案,它可以找到嵌套在列表列表中的字典内的键
。– Anthon

只要最后的print语句被修改,这在Python3中也适用。上面的所有解决方案都无法用于API响应,该API的响应具有嵌套在列表中的dicts内的列表等,但是这种方法效果很好。
Andy Forceno,

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

但是我认为原始版本更容易理解,因此我将其保留。


1
这也很好用,但是如果遇到直接包含字符串的列表(在我的示例中我忘记包含它),同样会遇到问题。我认为在最后两行之前添加isinstancea的检查可以dict解决此问题。
马特·斯温

1
感谢您的赞誉,但是我为获得代码的整洁而不是速度而感到自豪。
Alfe

1
95%的时间是。剩下的(很少)场合是一些时间限制可能迫使我选择速度更快的版本而不是更干净的版本。但是我不喜欢这样。这始终意味着将大量工作交给必须维护该代码的继任者。这是一个风险,因为我的继任者可能会感到困惑。然后,我将不得不写很多评论,也许是一个完整的文档来解释我的动机,计时实验,他们的结果等。这对我和所有同事来说都是很多工作,以使其正确完成。清洁更简单。
Alfe

2
@Alfe-感谢您的回答。我有一个需要提取字符串的所有出现在Elasticsearch的具体使用情况的嵌套字典,这是代码用稍作修改有用- stackoverflow.com/questions/40586020/...
SAURABH Hirani

1
这完全破坏了直接包含在列表中的列表。
Anthon

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

5

我只是想使用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的答案的优秀mod:简洁,并允许字典列表!我将其与@ bruno-bronosky的建议结合使用,以使用for key in keys。我也加入到第二个isinstance(list, tuple)的,甚至更多的品种。;)
Cometsong

4

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

def get_recursively(search_dict,field):
    “”“采用包含嵌套列表和字典的字典,
    并在所有字典中搜索该字段的关键字
    提供。
    “”
    fields_found = []

    对于键,在search_dict.iteritems()中的值:

        如果键==字段:
            fields_found.append(值)

        elif isinstance(value,dict):
            结果= get_recursively(值,字段)
            结果:
                fields_found.append(结果)

        elif isinstance(值,列表):
            对于有价物品:
                如果isinstance(item,dict):
                    more_results = get_recursively(项目,字段)
                    对于more_results中的another_result:
                        fields_found.append(another_result)

    返回fields_found

1
您可以使用fields_found.extend(more_results)代替运行另一个循环。在我看来,看起来会更干净一些。
萨比特

0

这是我的目的:

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}}
>>> keyHole('Me',findMe)
<generator object keyHole at 0x105eccb90>
>>> [ x for x in keyHole('Me',findMe) ]
['bop', 4]

0

如果要遍历键列表/键集,请跟踪@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]},而不是字符串key。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.