python 2.7 - resueltos - Método seguro de Python para obtener valor del diccionario anidado
lista dentro de un diccionario python (10)
Al combinar todas estas respuestas aquí y los pequeños cambios que hice, creo que esta función sería útil. es seguro, rápido y fácil de mantener.
def deep_get(dictionary, keys, default=None):
return reduce(lambda d, key: d.get(key, default) if isinstance(d, dict) else default, keys.split("."), dictionary)
Ejemplo:
>>> from functools import reduce
>>> def deep_get(dictionary, keys, default=None):
... return reduce(lambda d, key: d.get(key, default) if isinstance(d, dict) else default, keys.split("."), dictionary)
...
>>> person = {''person'':{''name'':{''first'':''John''}}}
>>> print (deep_get(person, "person.name.first"))
John
>>> print (deep_get(person, "person.name.lastname"))
None
>>> print (deep_get(person, "person.name.lastname", default="No lastname"))
No lastname
>>>
Tengo un diccionario anidado. ¿Hay una sola forma de obtener valores de forma segura?
try:
example_dict[''key1''][''key2'']
except KeyError:
pass
¿O quizás Python tiene un método como get()
para el diccionario anidado?
Aprovechando la respuesta de Yoav, un enfoque aún más seguro:
def deep_get(dictionary, *keys):
return reduce(lambda d, key: d.get(key, None) if isinstance(d, dict) else None, keys, dictionary)
Después de ver this para obtener atributos profundos, hice lo siguiente para obtener de manera segura los valores dict
anidados mediante la notación de puntos. Esto funciona para mí porque mis dicts
son objetos MongoDB deserializados, así que sé que los nombres de las teclas no contienen .
s. Además, en mi contexto, puedo especificar un valor falso ( None
) que no tengo en mis datos, por lo que puedo evitar el patrón try / except cuando llamo a la función.
from functools import reduce # Python 3
def deepgetitem(obj, item, fallback=None):
"""Steps through an item chain to get the ultimate value.
If ultimate value or path to value does not exist, does not raise
an exception and instead returns `fallback`.
>>> d = {''snl_final'': {''about'': {''_icsd'': {''icsd_id'': 1}}}}
>>> deepgetitem(d, ''snl_final.about._icsd.icsd_id'')
1
>>> deepgetitem(d, ''snl_final.about._sandbox.sbx_id'')
>>>
"""
def getitem(obj, name):
try:
return obj[name]
except (KeyError, TypeError):
return fallback
return reduce(getitem, item.split(''.''), obj)
Podrías usar get
dos veces:
example_dict.get(''key1'', {}).get(''key2'')
Esto devolverá None
si key2
o key2
no existen.
Tenga en cuenta que esto aún podría generar un AttributeError
si example_dict[''key1'']
existe, pero no es un dict (o un objeto similar a dict con un método get
). El código try..except
que publicaste generaría un TypeError
en su lugar si example_dict[''key1'']
es susceptible de suscripción.
Otra diferencia es que el try...except
cortocircuitos inmediatamente después de la primera clave faltante. La cadena de llamadas no lo hace.
Si desea conservar la sintaxis, example_dict[''key1''][''key2'']
pero no desea que se suba KeyErrors, entonces podría usar la receta Hasher :
class Hasher(dict):
# https://.com/a/3405143/190597
def __missing__(self, key):
value = self[key] = type(self)()
return value
example_dict = Hasher()
print(example_dict[''key1''])
# {}
print(example_dict[''key1''][''key2''])
# {}
print(type(example_dict[''key1''][''key2'']))
# <class ''__main__.Hasher''>
Tenga en cuenta que esto devuelve un Hasher vacío cuando falta una clave.
Como Hasher
es una subclase de dict
, puedes usar un Hasher de la misma manera que podrías usar un dict
. Todos los mismos métodos y sintaxis están disponibles, Hashers simplemente trata las claves que faltan de manera diferente.
Puede convertir un dict
regular en un Hasher
como este:
hasher = Hasher(example_dict)
y convierte un Hasher
a un dict
regular con la misma facilidad:
regular_dict = dict(hasher)
Otra alternativa es ocultar la fealdad en una función auxiliar:
def safeget(dct, *keys):
for key in keys:
try:
dct = dct[key]
except KeyError:
return None
return dct
Entonces, el resto de tu código puede mantenerse relativamente legible:
safeget(example_dict, ''key1'', ''key2'')
Si bien el enfoque de reducción es ordenado y breve, creo que un simple bucle es más fácil de asimilar. También he incluido un parámetro predeterminado.
def deep_get(_dict, keys, default=None):
for key in keys:
if isinstance(_dict, dict):
_dict = _dict.get(key, default)
else:
return default
return _dict
Como un ejercicio para entender cómo funcionaba la reducción de un trazador de líneas, hice lo siguiente. Pero, en última instancia, el enfoque de bucle me parece más intuitivo.
def deep_get(_dict, keys, default=None):
def _reducer(d, key):
if isinstance(d, dict):
return d.get(key, default)
return default
return reduce(_reducer, keys, _dict)
Uso
nested = {''a'': {''b'': {''c'': 42}}}
print deep_get(nested, [''a'', ''b''])
print deep_get(nested, [''a'', ''b'', ''z'', ''z''], default=''missing'')
También podrías usar python reduce :
def deep_get(dictionary, *keys):
return reduce(lambda d, key: d.get(key) if d else None, keys, dictionary)
Una adaptación de la respuesta de unutbu que encontré útil en mi propio código:
example_dict.setdefaut(''key1'', {}).get(''key2'')
Genera una entrada de diccionario para key1 si ya no tiene esa clave para evitar el KeyError. Si desea terminar un diccionario anidado que incluya ese emparejamiento de clave de todos modos como lo hice, esta parece ser la solución más fácil.
Una clase simple que puede envolver un dict, y recuperar basado en una clave:
class FindKey(dict):
def get(self, path, default=None):
keys = path.split(".")
val = None
for key in keys:
if val:
if isinstance(val, list):
val = [v.get(key, default) if v else None for v in val]
else:
val = val.get(key, default)
else:
val = dict.get(self, key, default)
if not val:
break
return val
Por ejemplo:
person = {''person'':{''name'':{''first'':''John''}}}
FindDict(person).get(''person.name.first'') # == ''John''
Si la clave no existe, devuelve None
por defecto. Puede anular eso utilizando una clave default=
en el contenedor FindDict
- por ejemplo`:
FindDict(person, default='''').get(''person.name.last'') # == doesn''t exist, so ''''
Una solución recursiva. No es el más eficiente, pero me parece un poco más legible que los otros ejemplos y no se basa en functools.
def deep_get(d, keys):
if not keys or d is None:
return d
return deep_get(d.get(keys[0]), keys[1:])
Ejemplo
d = {''meta'': {''status'': ''OK'', ''status_code'': 200}}
deep_get(d, [''meta'', ''status_code'']) # => 200
deep_get(d, [''garbage'', ''status_code'']) # => None
Una versión más pulida
def deep_get(d, keys, default=None):
"""
Example:
d = {''meta'': {''status'': ''OK'', ''status_code'': 200}}
deep_get(d, [''meta'', ''status_code'']) # => 200
deep_get(d, [''garbage'', ''status_code'']) # => None
deep_get(d, [''meta'', ''garbage''], default=''-'') # => ''-''
"""
assert type(keys) is list
if d is None:
return default
if not keys:
return d
return deep_get(d.get(keys[0]), keys[1:], default)
para recuperar claves de segundo nivel, puede hacer esto:
key2_value = (example_dict.get(''key1'') or {}).get(''key2'')