Элегантный способ проверить, существует ли вложенный ключ в python dict

Есть ли более читаемый способ проверить, существует ли ключ, закодированный в dict, без проверки каждого уровня независимо?

Давайте скажем, что мне нужно получить это значение в похороненном объекте (пример взята из Викидата):

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

Чтобы убедиться, что это не заканчивается ошибкой во время выполнения, необходимо либо проверить каждый уровень следующим образом:

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

Другим способом, который я могу решить, это обернуть это в конструкцию try catch, которая, как я чувствую, также довольно неудобна для такой простой задачи.

Я ищу что-то вроде:

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

который возвращает True, если все уровни существуют.

Ответ 1

Чтобы быть кратким, с Python вы должны доверять, что проще просить прощения, чем разрешения

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

Ответ

Вот как я разбираюсь с вложенными ключами dict:

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

Пример:

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

Вывод:

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

Он зацикливается на заданном element проверяя каждый ключ в заданном порядке.

Я предпочитаю это всем методам variable.get('key', {}) которые я нашел, потому что он следует за EAFP.

Функция, за исключением того, что keys_exists(dict_element_to_test, 'key_level_0', 'key_level_1', 'key_level_n',..) как: keys_exists(dict_element_to_test, 'key_level_0', 'key_level_1', 'key_level_n',..). Требуется как минимум два аргумента, элемент и один ключ, но вы можете добавить, сколько ключей вы хотите.

Если вам нужно использовать какую-либо карту, вы можете сделать что-то вроде:

expected_keys = ['spam', 'egg', 'bacon']
keys_exists(data, *expected_keys)

Ответ 2

Вы можете использовать .get со значениями по умолчанию:

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

но это почти наверняка менее понятно, чем использование try/except.

Ответ 3

Попробуйте/за исключением, по-видимому, самого питонического способа сделать это.
Должна работать следующая рекурсивная функция (возвращает None, если один из ключей не был найден в 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

Ответ 4

Вы можете использовать pydash для проверки, существует ли: http://pydash.readthedocs.io/en/latest/api.html#pydash.objects.has

Или получите значение (вы даже можете установить значение по умолчанию - вернуть, если его не существует): http://pydash.readthedocs.io/en/latest/api.html#pydash.objects.has

Вот пример:

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

Ответ 5

Я предлагаю вам использовать python-benedict, твердый подкласс python dict с полной поддержкой keypath и многими вспомогательными методами.

Вам просто нужно разыграть существующий дикт:

s = benedict(s)

Теперь ваш dict имеет полную поддержку keypath, и вы можете проверить, существует ли ключ питоническим способом, используя оператор in:

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

Здесь хранилище библиотеки и документация: https://github.com/fabiocaccamo/python-benedict

Ответ 6

Я написал библиотеку анализа данных, называемую dataknead для таких случаев, в основном потому, что я был разочарован JSON, API API Wikidata.

С помощью этой библиотеки вы можете сделать что-то вроде этого

from dataknead import Knead

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

if numid:
    # Do something with 'numeric-id'

Ответ 7

Способ "попробуй/кроме" - самый чистый, без соревнований. Тем не менее, это также считается исключением в моей IDE, которая останавливает выполнение во время отладки.

Кроме того, мне не нравится использовать исключения в качестве управляющих операторов в методе, что, по сути, и происходит с try/catch.

Вот краткое решение, которое не использует рекурсию и поддерживает значение по умолчанию:

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

Ответ 8

Если вы можете испытать строковое представление пути к объекту, тогда этот подход может работать для вас:

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

exists("lst['sublist']['item']")

Ответ 9

Это больно и безобразно, но я думаю, что это лучший способ без использования внешней библиотеки:

val = doc['data']['object']['id'] if 'data' in doc and 'object' in doc['data'] and 'id' in doc['data']['object'] else None