Existe-t-il un moyen plus lisible de vérifier si une clé enfouie dans un dict existe sans vérifier chaque niveau indépendamment?
Disons que je dois obtenir cette valeur dans un objet enterré (exemple tiré de Wikidata):
x = s['mainsnak']['datavalue']['value']['numeric-id']
Pour vous assurer que cela ne se termine pas par une erreur d'exécution, il est nécessaire de vérifier chaque niveau comme suit:
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']
L’autre façon de résoudre ce problème est d’englober cela dans une construction try catch
qui, à mon avis, est également assez délicate pour une tâche aussi simple.
Je cherche quelque chose comme:
x = exists(s['mainsnak']['datavalue']['value']['numeric-id'])
qui retourne True
si tous les niveaux existent.
Pour être bref, avec Python, vous devez savoir qu’il est plus facile de demander pardon que l’autorisation
try:
x = s['mainsnak']['datavalue']['value']['numeric-id']
except KeyError:
pass
Voici comment je traite les clés dict imbriquées:
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
Exemple:
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"))
Sortie:
spam (exists): True
spam > bacon (do not exists): False
spam > Egg (exists): True
spam > Egg > bacon (exists): True
Il boucle dans element
donné en testant chaque clé dans un ordre donné.
Je préfère ceci à toutes les méthodes variable.get('key', {})
que j'ai trouvées car elles suivent EAFP .
Fonction sauf à être appelée comme: keys_exists(dict_element_to_test, 'key_level_0', 'key_level_1', 'key_level_n', ..)
. Au moins deux arguments sont requis, l'élément et une clé, mais vous pouvez ajouter le nombre de clés souhaité.
Si vous devez utiliser un type de carte, vous pouvez faire quelque chose comme:
expected_keys = ['spam', 'Egg', 'bacon']
keys_exists(data, *expected_keys)
Vous pouvez utiliser .get
avec les valeurs par défaut:
s.get('mainsnak', {}).get('datavalue', {}).get('value', {}).get('numeric-id')
mais c'est presque certainement moins clair que d'utiliser try/except.
Try/except semble être le moyen le plus pythonique de le faire.
La fonction récursive suivante devrait fonctionner (ne renvoie aucune si l'une des clés n'a pas été trouvée dans le 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
Vous pouvez utiliser pydash
pour vérifier s’il existe: http://pydash.readthedocs.io/en/latest/api.html#pydash.objects.has
Ou obtenez la valeur (vous pouvez même définir default - à renvoyer si n'existe pas): http://pydash.readthedocs.io/en/latest/api.html#pydash.objects.has
Voici un exemple:
>>> get({'a': {'b': {'c': [1, 2, 3, 4]}}}, 'a.b.c[1]')
2
J'ai écrit une bibliothèque d'analyse de données appelée dataknead
pour des cas comme celui-ci, essentiellement parce que je suis frustré par le JSON, l'API Wikidata est également renvoyée.
Avec cette bibliothèque, vous pourriez faire quelque chose comme ça
from dataknead import Knead
numid = Knead(s).query("mainsnak/datavalue/value/numeric-id").data()
if numid:
# Do something with `numeric-id`
La méthode try/except est la plus propre, pas de contestation. Cependant, cela compte aussi comme une exception dans mon IDE, qui arrête l'exécution pendant le débogage.
De plus, je n'aime pas utiliser les exceptions comme instructions de contrôle intégrées à la méthode, ce qui correspond essentiellement à ce qui se passe avec try/catch.
Voici une solution courte qui n'utilise pas de récursivité et supporte une valeur par défaut:
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