Элегантный способ проверить, существует ли вложенный ключ в 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