web-dev-qa-db-fra.com

Rechercher toutes les occurrences d'une clé dans des dictionnaires et des listes python imbriqués

J'ai un dictionnaire comme celui-ci:

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

Fondamentalement, un dictionnaire avec des listes imbriquées, des dictionnaires et des chaînes de profondeur arbitraire.

Quelle est la meilleure façon de parcourir cela pour extraire les valeurs de chaque clé "id"? Je veux obtenir l'équivalent d'une requête XPath telle que "// id". La valeur de "id" est toujours une chaîne.

Donc, à partir de mon exemple, le résultat dont j'ai besoin est fondamentalement:

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

L'ordre n'est pas important.

53
Matt Swain

J'ai trouvé ce Q/A très intéressant, car il fournit plusieurs solutions différentes pour le même problème. J'ai pris toutes ces fonctions et les ai testées avec un objet dictionnaire complexe. Je devais supprimer deux fonctions du test, car elles comportaient trop d'échecs et ne permettaient pas de renvoyer des listes ou des dict en tant que valeurs, ce qui me semble essentiel, car une fonction doit être préparée pour presque n'importe quel données à viens.

J'ai donc pompé les autres fonctions dans 100 000 itérations via le module timeit et la sortie a abouti au résultat suivant:

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

Toutes les fonctions avaient la même aiguille à rechercher ('journalisation') et le même objet du dictionnaire, construit comme suit:

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]]
}

Toutes les fonctions ont donné le même résultat, mais les différences de temps sont spectaculaires! La fonction gen_dict_extract(k,o) est ma fonction adaptée à partir des fonctions ici. En fait, c'est assez semblable à la fonction find de Alfe, avec la différence principale que je vérifie si l'objet donné a une fonction itéritems, au cas où des chaînes sont passées pendant la récursion:

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

Donc, cette variante est la plus rapide et la plus sûre des fonctions ici. Et find_all_items est incroyablement lent et éloigné du deuxième get_recursivley le plus lent, tandis que le reste, à l'exception de dict_extract, est proche l'un de l'autre. Les fonctions fun et keyHole ne fonctionnent que si vous recherchez des chaînes.

Aspect d'apprentissage intéressant ici :)

44
hexerei software
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']
38
kev
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
14
Alfe
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'))
5
arainchi

Voici comment je l'ai fait. 

Cette fonction recherche de manière récursive un dictionnaire contenant des dictionnaires et des listes imbriqués. Il construit une liste appelée fields_found, qui contient la valeur pour chaque fois que le champ est trouvé. Le "champ" est la clé que je recherche dans le dictionnaire et dans ses listes et dictionnaires imbriqués.

 def get_recursively (search_dict, field): 
 "" "Prend un dict avec des listes imbriquées et dict, 
 Et cherche dans tous les dict une clé du champ 
 À condition que ..
" "" 
 fields_found = [] 

 pour key, valeur dans search_dict.iteritems (): 
.____. si clé == champ: 
 fields_found.append (valeur) 

 Elif isinstance (value, dict): 
 results = get_recursively (valeur, champ) 
 pour résultat résultat: 
 fields_found.append (result) 

 Elif isinstance (valeur, liste): 
 pour l'article en valeur: 
 si estinstance (item, dict): 
 more_results = get_recursively (élément, champ) 
 pour another_result in more_results: 
 fields_found.append (another_result) 

 retourner champs_trouvés 
4
Becca Petrin

Une autre variante, qui inclut le chemin imbriqué vers les résultats trouvés (note: cette version ne prend pas en compte les listes):

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
3
Dolan Antenucci

Je voulais simplement parcourir l'excellente réponse de @ hexerei-software en utilisant yield from et en acceptant les listes de niveau supérieur. 

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)
3
Chris

Voici mon coup de poignard à cela:

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]
0
AXE-Labs