recorrer - lista de diccionarios python
¿Tiene acceso a elementos del diccionario anidados a través de una lista de claves? (10)
- La solución aceptada no funcionará directamente para python3; necesitará una
from functools import reduce
. - También parece más pitónico utilizar un bucle
for
. Vea la cita de Novedades en Python 3.0 .Se eliminó
reduce()
. Usefunctools.reduce()
si realmente lo necesita; sin embargo, el 99 por ciento del tiempo un bucle explícito es más legible. - A continuación, la solución aceptada no establece claves anidadas no existentes (devuelve un
KeyError
) - vea la respuesta de @ eafit para una solución
Entonces, ¿por qué no utilizar el método sugerido de la pregunta de kolergy para obtener un valor?
def getFromDict(dataDict, mapList):
for k in mapList: dataDict = dataDict[k]
return dataDict
Y el código de la respuesta de @ eafit para establecer un valor:
def nested_set(dic, keys, value):
for key in keys[:-1]:
dic = dic.setdefault(key, {})
dic[keys[-1]] = value
Ambos trabajan directamente en python 2 y 3
Tengo una estructura de diccionario compleja a la que me gustaría acceder a través de una lista de claves para abordar el elemento correcto.
dataDict = {
"a":{
"r": 1,
"s": 2,
"t": 3
},
"b":{
"u": 1,
"v": {
"x": 1,
"y": 2,
"z": 3
},
"w": 3
}
}
maplist = ["a", "r"]
o
maplist = ["b", "v", "y"]
He creado el siguiente código que funciona, pero estoy seguro de que hay una manera mejor y más eficiente de hacerlo si alguien tiene una idea.
# Get a given data from a dictionary with position provided as a list
def getFromDict(dataDict, mapList):
for k in mapList: dataDict = dataDict[k]
return dataDict
# Set a given data in a dictionary with position provided as a list
def setInDict(dataDict, mapList, value):
for k in mapList[:-1]: dataDict = dataDict[k]
dataDict[mapList[-1]] = value
¿Qué hay de usar las funciones recursivas?
Para obtener un valor:
def getFromDict(dataDict, maplist):
first, rest = maplist[0], maplist[1:]
if rest:
# if `rest` is not empty, run the function recursively
return getFromDict(dataDict[first], rest)
else:
return dataDict[first]
Y para establecer un valor:
def setInDict(dataDict, maplist, value):
first, rest = maplist[0], maplist[1:]
if rest:
try:
if not isinstance(dataDict[first], dict):
# if the key is not a dict, then make it a dict
dataDict[first] = {}
except KeyError:
# if key doesn''t exist, create one
dataDict[first] = {}
setInDict(dataDict[first], rest, value)
else:
dataDict[first] = value
En lugar de tomar un golpe de rendimiento cada vez que desea buscar un valor, ¿qué tal aplanar el diccionario una vez y luego simplemente buscar la tecla como b:v:y
def flatten(mydict):
new_dict = {}
for key,value in mydict.items():
if type(value) == dict:
_dict = {'':''.join([key, _key]):_value for _key, _value in flatten(value).items()}
new_dict.update(_dict)
else:
new_dict[key]=value
return new_dict
dataDict = {
"a":{
"r": 1,
"s": 2,
"t": 3
},
"b":{
"u": 1,
"v": {
"x": 1,
"y": 2,
"z": 3
},
"w": 3
}
}
flat_dict = flatten(dataDict)
print flat_dict
{''b:w'': 3, ''b:u'': 1, ''b:v:y'': 2, ''b:v:x'': 1, ''b:v:z'': 3, ''a:r'': 1, ''a:s'': 2, ''a:t'': 3}
De esta manera, simplemente puede buscar elementos utilizando flat_dict[''b:v:y'']
que le dará 1
.
Y en lugar de atravesar el diccionario en cada búsqueda, puede acelerar esto al acoplar el diccionario y guardar el resultado de modo que una búsqueda desde el inicio en frío significaría cargar el diccionario aplanado y simplemente realizar una búsqueda de clave / valor sin atravesar
Esta biblioteca puede ser útil: https://github.com/akesterson/dpath-python
Una biblioteca de Python para acceder y buscar diccionarios a través de / slashed / paths alapath
Básicamente le permite agrupar un diccionario como si fuera un sistema de archivos.
Estilo Pure Python, sin importar:
def nested_set(element, value, *keys):
if type(element) is not dict:
raise AttributeError(''nested_set() expects dict as first argument.'')
if len(keys) < 2:
raise AttributeError(''nested_set() expects at least three arguments, not enough given.'')
_keys = keys[:-1]
_element = element
for key in _keys:
_element = _element[key]
_element[keys[-1]] = value
example = {"foo": { "bar": { "baz": "ok" } } }
keys = [''foo'', ''bar'']
nested_set(example, "yay", *keys)
print(example)
Salida
{''foo'': {''bar'': ''yay''}}
Resolvió esto con recursión:
def get(d,l):
if len(l)==1: return d[l[0]]
return get(d[l[0]],l[1:])
Usando tu ejemplo:
dataDict = {
"a":{
"r": 1,
"s": 2,
"t": 3
},
"b":{
"u": 1,
"v": {
"x": 1,
"y": 2,
"z": 3
},
"w": 3
}
}
maplist1 = ["a", "r"]
maplist2 = ["b", "v", "y"]
print(get(dataDict, maplist1)) # 1
print(get(dataDict, maplist2)) # 2
Si también quieres la capacidad de trabajar con json arbitrario, incluidas las listas anidadas y los dicts, y manejar bien las rutas de búsqueda no válidas, esta es mi solución:
from functools import reduce
def get_furthest(s, path):
''''''
Gets the furthest value along a given key path in a subscriptable structure.
subscriptable, list -> any
:param s: the subscriptable structure to examine
:param path: the lookup path to follow
:return: a tuple of the value at the furthest valid key, and whether the full path is valid
''''''
def step_key(acc, key):
s = acc[0]
if isinstance(s, str):
return (s, False)
try:
return (s[key], acc[1])
except LookupError:
return (s, False)
return reduce(step_key, path, (s, True))
def get_val(s, path):
val, successful = get_furthest(s, path)
if successful:
return val
else:
raise LookupError(''Invalid lookup path: {}''.format(path))
def set_val(s, path, value):
get_val(s, path[:-1])[path[-1]] = value
Una forma alternativa si no desea generar errores si una de las claves está ausente (para que su código principal pueda ejecutarse sin interrupción):
def get_value(self,your_dict,*keys):
curr_dict_ = your_dict
for k in keys:
v = curr_dict.get(k,None)
if v is None:
break
if isinstance(v,dict):
curr_dict = v
return v
En este caso, si alguna de las teclas de entrada no está presente, se devuelve None, que se puede utilizar como verificación en el código principal para realizar una tarea alternativa.
Usar reducir es inteligente, pero el método de configuración del OP puede tener problemas si las claves principales no existen previamente en el diccionario anidado. Dado que esta es la primera publicación SO que vi para este tema en mi búsqueda en Google, me gustaría hacerlo un poco mejor.
El método set en ( Establecer un valor en un diccionario python anidado dada una lista de índices y valor ) parece más robusto para las claves parentales faltantes. Para copiarlo:
def nested_set(dic, keys, value):
for key in keys[:-1]:
dic = dic.setdefault(key, {})
dic[keys[-1]] = value
Además, puede ser conveniente tener un método que atraviese el árbol de claves y obtener todas las rutas de clave absolutas, para las cuales he creado:
def keysInDict(dataDict, parent=[]):
if not isinstance(dataDict, dict):
return [tuple(parent)]
else:
return reduce(list.__add__,
[keysInDict(v,parent+[k]) for k,v in dataDict.items()], [])
Uno de sus usos es convertir el árbol anidado en un FrameFrame de pandas, utilizando el siguiente código (suponiendo que todas las hojas en el diccionario anidado tengan la misma profundidad).
def dict_to_df(dataDict):
ret = []
for k in keysInDict(dataDict):
v = np.array( getFromDict(dataDict, k), )
v = pd.DataFrame(v)
v.columns = pd.MultiIndex.from_product(list(k) + [v.columns])
ret.append(v)
return reduce(pd.DataFrame.join, ret)
Use reduce()
para recorrer el diccionario:
from functools import reduce # forward compatibility for Python 3
import operator
def getFromDict(dataDict, mapList):
return reduce(operator.getitem, mapList, dataDict)
y reutilizar getFromDict
para encontrar la ubicación para almacenar el valor de setInDict()
:
def setInDict(dataDict, mapList, value):
getFromDict(dataDict, mapList[:-1])[mapList[-1]] = value
Se necesita todo menos el último elemento en mapList
para encontrar el diccionario ''padre'' para agregar el valor, luego use el último elemento para establecer el valor en la clave correcta.
Manifestación:
>>> getFromDict(dataDict, ["a", "r"])
1
>>> getFromDict(dataDict, ["b", "v", "y"])
2
>>> setInDict(dataDict, ["b", "v", "w"], 4)
>>> import pprint
>>> pprint.pprint(dataDict)
{''a'': {''r'': 1, ''s'': 2, ''t'': 3},
''b'': {''u'': 1, ''v'': {''w'': 4, ''x'': 1, ''y'': 2, ''z'': 3}, ''w'': 3}}