web-dev-qa-db-de.com

Eine elegante Möglichkeit, zu überprüfen, ob ein verschachtelter Schlüssel in einem Python-Dikt vorhanden ist

Gibt es eine lesbarere Methode, um zu überprüfen, ob ein in einem Diktier vergrabener Schlüssel vorhanden ist, ohne jede Ebene einzeln zu prü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']

Der andere Weg, den ich mir vorstellen kann, um dies zu lösen, besteht darin, dies in ein try catch-Konstrukt einzuwickeln, das ich für eine so einfache Aufgabe auch ziemlich unangenehm finde.

Ich suche nach etwas wie:

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

was True zurückgibt, wenn alle Ebenen vorhanden sind.

29
loomi

Um es kurz zu machen, bei Python müssen Sie darauf vertrauen, dass es leichter um Vergebung als Erlaubnis zu bitten

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

Die Antwort

So gehe ich mit verschachtelten Diktierschlüssel um:

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

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

Sie führt eine Schleife in gegebener element durch, die jeden Schlüssel in der angegebenen Reihenfolge testet.

Ich bevorzuge dies allen variable.get('key', {})-Methoden, die ich gefunden habe, weil es EAFP folgt.

Funktion, außer wie folgt aufgerufen zu werden: 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 die Anzahl der gewünschten Schlüssel hinzufügen.

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

expected_keys = ['spam', 'Egg', 'bacon']
keys_exists(data, *expected_keys)
56
Arount

Sie können .get mit den Standardeinstellungen verwenden:

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

aber das ist fast sicher weniger klar als try/mit Ausnahme.

9
Daniel Roseman

Ich schlage vor, dass Sie python-benedict, eine feste python dict-Unterklasse mit vollständiger Keypath-Unterstützung und vielen Dienstprogrammmethoden.

Sie müssen nur Ihr bestehendes Diktat besetzen:

s = benedict(s)

Ihr Diktat wird jetzt vollständig von Keypath unterstützt, und Sie können überprüfen, ob der Schlüssel auf pythonische Weise vorhanden ist , indem Sie den Operator in verwenden:

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

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

4
Fabio Caccamo

Try/Exception scheint der Pythonic-Weg zu sein.
.__ Die folgende rekursive Funktion sollte funktionieren (gibt None zurück, wenn einer der Schlüssel nicht im Dikt 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
4
Maurice Meyer

Sie können pydash verwenden, um zu prüfen, ob es vorhanden ist: http://pydash.readthedocs.io/de/latest/api.html#pydash.objects.has

Oder holen Sie sich den Wert (Sie können sogar einen Standardwert festlegen, um zurückzugeben, falls nicht vorhanden): http://pydash.readthedocs.io/de/latest/api.html#pydash.objects.has

Hier ist ein Beispiel:

>>> get({'a': {'b': {'c': [1, 2, 3, 4]}}}, 'a.b.c[1]')
2
3
Alexander

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']")
1
geotheory

Ich habe eine Datenanalyse-Bibliothek namens dataknead für solche Fälle geschrieben, im Wesentlichen, weil ich durch die JSON-Version frustriert wurde, die auch die Wikidata-API 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`
1
Husky

Der Try/Except-Weg ist der sauberste, kein Wettbewerb. Es gilt jedoch auch als Ausnahme in meiner IDE, wodurch die Ausführung beim Debuggen angehalten wird. 

Außerdem mag ich es nicht, Ausnahmen als In-Method-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
0
Houen