que - Forma elegante de verificar si existe una clave anidada en un dict de Python
que es un diccionario en python (8)
¿Hay alguna forma más legible de verificar si existe una clave enterrada en un dict sin verificar cada nivel de forma independiente?
Digamos que necesito obtener este valor en un objeto enterrado (ejemplo tomado de Wikidata):
x = s[''mainsnak''][''datavalue''][''value''][''numeric-id'']
Para asegurarse de que esto no termine con un error de tiempo de ejecución, es necesario verificar cada nivel de la siguiente manera:
if ''mainsnak'' in s and ''datavalue'' in s[''mainsnak''] and ''value'' in s[''mainsnak''][''datavalue''] and ''nurmeric-id'' in s[''mainsnak''][''datavalue''][''value'']:
x = s[''mainsnak''][''datavalue''][''value''][''numeric-id'']
La otra forma en que puedo pensar para resolver esto es envolver esto en una construcción
try catch
que siento que también es bastante incómoda para una tarea tan simple.
Estoy buscando algo como:
x = exists(s[''mainsnak''][''datavalue''][''value''][''numeric-id''])
que devuelve
True
si todos los niveles existen.
Escribí una biblioteca de análisis de datos llamada
dataknead
para casos como este, básicamente porque el JSON me frustró y la API de Wikidata también regresa.
Con esa biblioteca podrías hacer algo como esto
from dataknead import Knead
numid = Knead(s).query("mainsnak/datavalue/value/numeric-id").data()
if numid:
# Do something with `numeric-id`
Intentar / excepto parece ser la forma más pitónica de hacerlo.
La siguiente función recursiva debería funcionar (devuelve None si no se encontró una de las claves en el dict):
def exists(obj, chain):
_key = chain.pop(0)
if _key in obj:
return exists(obj[_key], chain) if chain else obj[_key]
myDict ={
''mainsnak'': {
''datavalue'': {
''value'': {
''numeric-id'': 1
}
}
}
}
result = exists(myDict, [''mainsnak'', ''datavalue'', ''value'', ''numeric-id''])
print(result)
>>> 1
La forma de prueba / excepción es la más limpia, sin competencia. Sin embargo, también cuenta como una excepción en mi IDE, que detiene la ejecución durante la depuración.
Además, no me gusta usar excepciones como declaraciones de control en el método, que es esencialmente lo que está sucediendo con el try / catch.
Aquí hay una solución corta que no utiliza la recursividad y admite un valor predeterminado:
def chained_dict_lookup(lookup_dict, keys, default=None):
_current_level = lookup_dict
for key in keys:
if key in _current_level:
_current_level = _current_level[key]
else:
return default
return _current_level
Le sugiero que use
python-benedict
, una subclase sólida de python dict con soporte completo de keypath y muchos métodos de utilidad.
Solo necesita emitir su dict existente:
>>> get({''a'': {''b'': {''c'': [1, 2, 3, 4]}}}, ''a.b.c[1]'')
2
Ahora su dict tiene soporte completo para keypath y puede verificar si la clave existe en la forma pitónica, utilizando el operador in :
s = benedict(s)
Aquí el repositorio de la biblioteca y la documentación: https://github.com/fabiocaccamo/python-benedict
Para ser breve, con Python debes confiar en que es más fácil pedir perdón que permiso
try:
x = s[''mainsnak''][''datavalue''][''value''][''numeric-id'']
except KeyError:
pass
La respuesta
Así es como trato con claves dict anidadas:
def keys_exists(element, *keys):
''''''
Check if *keys (nested) exists in `element` (dict).
''''''
if type(element) is not dict:
raise AttributeError(''keys_exists() expects dict as first argument.'')
if len(keys) == 0:
raise AttributeError(''keys_exists() expects at least two arguments, one given.'')
_element = element
for key in keys:
try:
_element = _element[key]
except KeyError:
return False
return True
Ejemplo:
data = {
"spam": {
"egg": {
"bacon": "Well..",
"sausages": "Spam egg sausages and spam",
"spam": "does not have much spam in it"
}
}
}
print ''spam (exists): {}''.format(keys_exists(data, "spam"))
print ''spam > bacon (do not exists): {}''.format(keys_exists(data, "spam", "bacon"))
print ''spam > egg (exists): {}''.format(keys_exists(data, "spam", "egg"))
print ''spam > egg > bacon (exists): {}''.format(keys_exists(data, "spam", "egg", "bacon"))
Salida:
spam (exists): True
spam > bacon (do not exists): False
spam > egg (exists): True
spam > egg > bacon (exists): True
Se repite en un
element
dado
element
prueba cada tecla en un orden dado.
Prefiero esto a todos los métodos
variable.get(''key'', {})
que encontré porque sigue a
EAFP
.
Función excepto para ser llamada como:
keys_exists(dict_element_to_test, ''key_level_0'', ''key_level_1'', ''key_level_n'', ..)
.
Se requieren al menos dos argumentos, el elemento y una clave, pero puede agregar cuántas claves desea.
Si necesita usar un tipo de mapa, puede hacer algo como:
expected_keys = [''spam'', ''egg'', ''bacon'']
keys_exists(data, *expected_keys)
Puede usar
.get
con los valores predeterminados:
s.get(''mainsnak'', {}).get(''datavalue'', {}).get(''value'', {}).get(''numeric-id'')
pero esto es casi seguro menos claro que usar try / except.
Puede usar
pydash
para verificar si existe:
http://pydash.readthedocs.io/en/latest/api.html#pydash.objects.has
O bien, obtenga el valor (incluso puede establecer el valor predeterminado, para devolverlo si no existe): http://pydash.readthedocs.io/en/latest/api.html#pydash.objects.has
Aquí hay un ejemplo:
if ''mainsnak.datavalue.value.numeric-id'' in s:
# do stuff
Si puede sufrir la prueba de una representación de cadena de la ruta del objeto, entonces este enfoque podría funcionar para usted:
def exists(str):
try:
eval(str)
return True
except:
return False
exists("lst[''sublist''][''item'']")