recorrer - lista de diccionarios python
Encuentra todas las apariciones de una clave en diccionarios y listas de python anidados (8)
Tengo un diccionario como este:
{ "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" }] } ] }
Básicamente un diccionario con listas anidadas, diccionarios y cadenas, de profundidad arbitraria.
¿Cuál es la mejor manera de atravesar esto para extraer los valores de cada tecla "id"? Quiero lograr el equivalente de una consulta XPath como "// id". El valor de "id" es siempre una cadena.
Entonces, de mi ejemplo, el resultado que necesito es básicamente:
["abcde", "qwerty", "xyz", "fghi", "asdf", "yuiop"]
El orden no es importante.
Aquí está mi puñalada:
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]
Así es como lo hice.
Esta función busca de forma recursiva un diccionario que contiene diccionarios y listas anidados. Construye una lista llamada fields_found, que contiene el valor de cada vez que se encuentra el campo. El ''campo'' es la clave que estoy buscando en el diccionario y sus listas y diccionarios anidados.
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
Encontré este Q / A muy interesante, ya que ofrece varias soluciones diferentes para el mismo problema. Tomé todas estas funciones y las probé con un objeto de diccionario complejo. Tuve que quitar dos funciones de la prueba porque tenían que fallar muchos resultados y no admitían la devolución de listas o dictados como valores, que considero esenciales, ya que una función debe prepararse para casi todos los datos que vendrán.
Así que bombeé las otras funciones en 100.000 iteraciones a través del módulo de tiempo y la salida llegó al siguiente resultado:
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)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Todas las funciones tenían la misma aguja para buscar (''logging'') y el mismo objeto de diccionario, que está construido así:
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]]
}
Todas las funciones entregaron el mismo resultado, pero las diferencias de tiempo son dramáticas. La función gen_dict_extract(k,o)
es mi función adaptada de las funciones aquí, en realidad es muy parecida a la función find
de Alfe, con la diferencia principal, que estoy comprobando si el objeto dado tiene la función iteritems, en caso de que las cadenas sean pasado durante la recursión:
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
Entonces, esta variante es la más rápida y segura de las funciones aquí. Y find_all_items
es increíblemente lento y está lejos del segundo get_recursivley
más get_recursivley
mientras que el resto, excepto dict_extract
, está cerca el uno del otro. Las funciones fun
y keyHole
solo funcionan si buscas cadenas.
Interesante aspecto de aprendizaje aquí :)
Otra variación, que incluye la ruta anidada a los resultados encontrados ( nota: esta versión no tiene en cuenta las listas ):
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
Solo quería repetir la excelente respuesta de @hexerei-software utilizando el yield from
y aceptando listas de nivel superior.
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)
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''))
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'']
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