Elegante Möglichkeit zu überprüfen, ob ein verschachtelter Schlüssel in einem Diktat vorhanden ist?

78

Gibt es eine besser lesbare Möglichkeit, um zu überprüfen, ob ein in einem Diktat vergrabener Schlüssel vorhanden ist, ohne jede Ebene einzeln zu überprüfen?

Nehmen wir an, ich muss diesen Wert in einem vergrabenen Objekt erhalten (Beispiel aus Wikidata):

x = s['mainsnak']['datavalue']['value']['numeric-id']

Um sicherzustellen, dass dies nicht mit einem Laufzeitfehler endet, müssen Sie entweder jede Ebene wie folgt überprüfen:

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']

Die andere Möglichkeit, dies zu lösen, besteht darin, dies in ein try catchKonstrukt zu packen, das meiner Meinung nach für eine so einfache Aufgabe ebenfalls ziemlich umständlich ist.

Ich suche so etwas wie:

x = exists(s['mainsnak']['datavalue']['value']['numeric-id'])

Dies wird zurückgegeben, Truewenn alle Ebenen vorhanden sind.

loomi
quelle

Antworten:

126

Kurz gesagt, mit Python müssen Sie darauf vertrauen, dass es einfacher ist, um Vergebung als um Erlaubnis zu bitten

try:
    x = s['mainsnak']['datavalue']['value']['numeric-id']
except KeyError:
    pass

Die Antwort

So gehe ich mit verschachtelten Diktatschlüsseln um:

def keys_exists(element, *keys):
    '''
    Check if *keys (nested) exists in `element` (dict).
    '''
    if not isinstance(element, 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

Beispiel:

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"))

Ausgabe:

spam (exists): True
spam > bacon (do not exists): False
spam > egg (exists): True
spam > egg > bacon (exists): True

Es wird elementjeder Schlüssel in einer bestimmten Reihenfolge getestet.

Ich bevorzuge dies für alle variable.get('key', {})Methoden, die ich gefunden habe, weil es EAFP folgt .

Funktion außer aufgerufen zu werden wie : keys_exists(dict_element_to_test, 'key_level_0', 'key_level_1', 'key_level_n', ..). Es sind mindestens zwei Argumente erforderlich, das Element und ein Schlüssel. Sie können jedoch hinzufügen, wie viele Schlüssel Sie möchten.

Wenn Sie eine Art Karte verwenden müssen, können Sie Folgendes tun:

expected_keys = ['spam', 'egg', 'bacon']
keys_exists(data, *expected_keys)
Arount
quelle
Ja, wie bereits erwähnt, ist dies eine gültige Lösung. Aber stellen Sie sich eine Funktion vor, die auf das Zehnfache einer solchen Variablen zugreift. Alle try exceptAnweisungen hinterlassen ein ziemliches Aufblähen.
Loomi
@loomi Sie können eine kleine Funktion dieser try-exceptLogik machen und dies einfach jedes Mal
aufrufen
@loomi wickeln Sie es in eine Funktion.
juanpa.arrivillaga
1
"In zwei Worten, mit Python müssen Sie darauf vertrauen, dass es einfacher ist, um Vergebung als um Erlaubnis zu bitten" verwendet viel mehr als zwei Wörter.
user2357112 unterstützt Monica
1
Tolle Antwort, aber eines sollte geändert werden: if type(element) is not dictzu if not isinstance(element, dict). Auf diese Weise funktioniert es auch für Typen wie OrderedDict.
Maxxim
16

Sie können .getmit Standardeinstellungen verwenden:

s.get('mainsnak', {}).get('datavalue', {}).get('value', {}).get('numeric-id')

Dies ist jedoch mit ziemlicher Sicherheit weniger klar als die Verwendung von try / Except.

Daniel Roseman
quelle
1
Und was auch immer Sie den letzten getals Standardwert angeben, es könnte einfach der Wert von sein s['mainsnak']['datavalue']['value']['numeric-id'].
Timgeb
4
Ich habe dieses Konstrukt oft benutzt und bin gerade von diesem in den Fuß geschossen worden. Seien Sie vorsichtig, wenn Sie das obige Beispiel verwenden, denn wenn das "getted" -Element tatsächlich existiert und nicht diktiert ist (oder ein Objekt, das Sie aufrufen können get) (keines ist mein Fall), wird dies mit 'NoneType' object has no attribute 'get'oder einem beliebigen Typ enden, den Sie dort haben.
Dunkelloser
9

Try / Except scheint der pythonischste Weg zu sein, dies zu tun.
Die folgende rekursive Funktion sollte funktionieren (gibt None zurück, wenn einer der Schlüssel im Diktat nicht gefunden wurde):

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
Maurice Meyer
quelle
Wie würden Sie dies für Arrays tun, z. B. wenn 'Wert' ein Array von 'numerischen IDs' wäre? Ergebnis = existiert (myDict, ['mainsnak', 'Datenwert', 'Wert [0]', 'numerische ID'] )?
Dss
@Maurice Meyer: Was ist, wenn 'mainsnak2', 'mainsnak3' usw. existiert (wie 'mainsnak' bleibt das innere Wörterbuch gleich). Können wir in diesem Fall überprüfen, ob in allen 'mainsnak', 'mainsnak2' und 'mainsnak3' 'Datenwert' vorhanden ist?
StackGuru
4

Der Versuch / außer Weg ist der sauberste, kein Wettbewerb. Es zählt jedoch auch als Ausnahme in meiner IDE, die die Ausführung während des Debuggens anhält.

Darüber hinaus mag ich es nicht, Ausnahmen als methodeninterne Steueranweisungen zu verwenden, was im Wesentlichen mit try / catch geschieht.

Hier ist eine kurze Lösung, die keine Rekursion verwendet und einen Standardwert unterstützt:

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
Houen
quelle
Ich mag diese Lösung :) ... Nur eine Anmerkung hier. irgendwanncurrent_level[key] kann auf einen Wert und nicht auf ein inneres Diktat verweisen. Wenn Sie dies verwenden, achten Sie darauf, dass current_leveles sich nicht um einen String, einen Float oder etwas anderes handelt.
Jordan Simba
4

Python 3.8 +

dictionary = {
    "main_key": {
        "sub_key": "value",
    },
}

if sub_key_value := dictionary.get("main_key", {}).get("sub_key"):
    print(f"The key 'sub_key' exists in dictionary[main_key] and it's value is {sub_key_value}")
else:
    print("Key 'sub_key' doesn't exists")
Lucas Vazquez
quelle
SyntaxError: Ungültige Syntax bei if key_exists: = dictionary.get ("key_1", {}). Get ("key_2"):
aysh
@aysh Es ist Python 3.8 Beispiel
Lucas Vazquez
4

Ich empfehle Ihnen python-benedict, eine solide Python-Diktat-Unterklasse mit vollständiger Schlüsselpfadunterstützung und vielen Dienstprogrammmethoden zu verwenden.

Sie müssen nur Ihr bestehendes Diktat umsetzen:

s = benedict(s)

Jetzt unterstützt Ihr Diktat die vollständige Schlüsselpfadunterstützung und Sie können mithilfe des Operators in überprüfen, ob der Schlüssel pythonisch vorhanden ist :

if 'mainsnak.datavalue.value.numeric-id' in s:
    # do stuff

Hier das Bibliotheksrepository und die Dokumentation: https://github.com/fabiocaccamo/python-benedict

Hinweis: Ich bin der Autor dieses Projekts

Fabio Caccamo
quelle
2

Ich hatte das gleiche Problem und kürzlich tauchte die Python-Bibliothek auf:
https://pypi.org/project/dictor/
https://github.com/perfecto25/dictor

Also in deinem Fall:

from dictor import dictor

x = dictor(s, 'mainsnak.datavalue.value.numeric-id')

Persönliche Anmerkung:
Ich mag den Namen eines Diktors nicht, da er nicht andeutet, was er tatsächlich tut. Also benutze ich es wie folgt:

from dictor import dictor as extract
x = extract(s, 'mainsnak.datavalue.value.numeric-id')

Könnte nicht besser benannt werden als extract. Fühlen Sie sich frei zu kommentieren, wenn Sie eine brauchbarere Benennung finden. safe_get, robust_getFühlte sich nicht richtig für meinen Fall zu spüren.

dunkellos
quelle
1

Ich habe eine Datenanalysebibliothek geschrieben, die für solche datakneadFälle aufgerufen wurde , im Grunde genommen, weil ich von JSON frustriert war und die Wikidata-API ebenfalls zurückgibt.

Mit dieser Bibliothek könnten Sie so etwas tun

from dataknead import Knead

numid = Knead(s).query("mainsnak/datavalue/value/numeric-id").data()

if numid:
    # Do something with `numeric-id`
Heiser
quelle
1

Wenn Sie eine Zeichenfolgendarstellung des Objektpfads testen müssen, funktioniert dieser Ansatz möglicherweise für Sie:

def exists(str):
    try:
        eval(str)
        return True
    except:
        return False

exists("lst['sublist']['item']")
Geotheorie
quelle
aber im Rahmen dieser Funktion ist "lst" nicht definiert
Dss
1

Ein anderer Weg:

def does_nested_key_exists(dictionary, nested_key):
    exists = nested_key in dictionary
    if not exists:
        for key, value in dictionary.items():
            if isinstance(value, dict):
                exists = exists or does_nested_key_exists(value, nested_key)
    return exists
Freude jedidja Ndjama
quelle
Was ist do_nested_key_exists (Wert, nested_key) hier
aysh