У меня есть вложенный словарь. Есть только один способ безопасно вывести ценности?
try:
example_dict['key1']['key2']
except KeyError:
pass
Или, возможно, в Python есть метод, подобный get()
для вложенного словаря?
У меня есть вложенный словарь. Есть только один способ безопасно вывести ценности?
try:
example_dict['key1']['key2']
except KeyError:
pass
Или, возможно, в Python есть метод, подобный get()
для вложенного словаря?
Вы можете использовать get
дважды:
example_dict.get('key1', {}).get('key2')
Это вернет None
, если не существует key1
или key2
.
Обратите внимание, что это все равно может привести к появлению AttributeError
, если example_dict['key1']
существует, но не является dict (или диктоподобным объектом с методом get
). Вы отправили try..except
код, который вы отправили, вместо TypeError
, если example_dict['key1']
не подлежит подписке.
Другое отличие состоит в том, что короткое замыкание try...except
сразу после первого отсутствующего ключа. Цепочка вызовов get
не работает.
Если вы хотите сохранить синтаксис example_dict['key1']['key2']
, но не хотите, чтобы он когда-либо поднимал KeyErrors, вы можете использовать рецепт Хашера:
class Hasher(dict):
# /questions/35890/generating-dictionary-keys-on-the-fly/261439#261439
def __missing__(self, key):
value = self[key] = type(self)()
return value
example_dict = Hasher()
print(example_dict['key1'])
# {}
print(example_dict['key1']['key2'])
# {}
print(type(example_dict['key1']['key2']))
# <class '__main__.Hasher'>
Обратите внимание, что это возвращает пустой Hasher, когда отсутствует ключ.
Так как Hasher
является подклассом dict
, вы можете использовать Hasher так же, как вы могли бы использовать dict
. Все те же методы и синтаксис доступны, Хашеры просто обрабатывают отсутствующие ключи по-разному.
Вы можете преобразовать обычный dict
в Hasher
следующим образом:
hasher = Hasher(example_dict)
и конвертировать a Hasher
в обычный dict
так же легко:
regular_dict = dict(hasher)
Другой альтернативой является скрыть уродство в вспомогательной функции:
def safeget(dct, *keys):
for key in keys:
try:
dct = dct[key]
except KeyError:
return None
return dct
Таким образом, остальная часть вашего кода может оставаться относительно читаемой:
safeget(example_dict, 'key1', 'key2')
Вы также можете использовать python reduce:
def deep_get(dictionary, *keys):
return reduce(lambda d, key: d.get(key) if d else None, keys, dictionary)
Объединив все эти ответы здесь и небольшие изменения, которые я сделал, я думаю, что эта функция будет полезна. его безопасный, быстрый, легко ремонтируемый.
def deep_get(dictionary, keys, default=None):
return reduce(lambda d, key: d.get(key, default) if isinstance(d, dict) else default, keys.split("."), dictionary)
Пример:
>>> from functools import reduce
>>> def deep_get(dictionary, keys, default=None):
... return reduce(lambda d, key: d.get(key, default) if isinstance(d, dict) else default, keys.split("."), dictionary)
...
>>> person = {'person':{'name':{'first':'John'}}}
>>> print (deep_get(person, "person.name.first"))
John
>>> print (deep_get(person, "person.name.lastname"))
None
>>> print (deep_get(person, "person.name.lastname", default="No lastname"))
No lastname
>>>
Основываясь на ответе Йоава, еще более безопасный подход:
def deep_get(dictionary, *keys):
return reduce(lambda d, key: d.get(key, None) if isinstance(d, dict) else None, keys, dictionary)
В то время как подход сокращения является аккуратным и коротким, я думаю, что простой цикл легче перебирать. Я также включил параметр по умолчанию.
def deep_get(_dict, keys, default=None):
for key in keys:
if isinstance(_dict, dict):
_dict = _dict.get(key, default)
else:
return default
return _dict
Как упражнение, чтобы понять, как работает однострочный снимок, я сделал следующее. Но в конечном итоге петлевой подход кажется мне более интуитивным.
def deep_get(_dict, keys, default=None):
def _reducer(d, key):
if isinstance(d, dict):
return d.get(key, default)
return default
return reduce(_reducer, keys, _dict)
Использование
nested = {'a': {'b': {'c': 42}}}
print deep_get(nested, ['a', 'b'])
print deep_get(nested, ['a', 'b', 'z', 'z'], default='missing')
Рекурсивное решение. Он не самый эффективный, но я считаю его более читабельным, чем другие примеры, и он не зависит от functools.
def deep_get(d, keys):
if not keys or d is None:
return d
return deep_get(d.get(keys[0]), keys[1:])
пример
d = {'meta': {'status': 'OK', 'status_code': 200}}
deep_get(d, ['meta', 'status_code']) # => 200
deep_get(d, ['garbage', 'status_code']) # => None
Более отполированная версия
def deep_get(d, keys, default=None):
"""
Example:
d = {'meta': {'status': 'OK', 'status_code': 200}}
deep_get(d, ['meta', 'status_code']) # => 200
deep_get(d, ['garbage', 'status_code']) # => None
deep_get(d, ['meta', 'garbage'], default='-') # => '-'
"""
assert type(keys) is list
if d is None:
return default
if not keys:
return d
return deep_get(d.get(keys[0]), keys[1:], default)
для получения второго ключа уровня, вы можете сделать это:
key2_value = (example_dict.get('key1') or {}).get('key2')
Простой класс, который может обернуть dict и получить на основе ключа:
class FindKey(dict):
def get(self, path, default=None):
keys = path.split(".")
val = None
for key in keys:
if val:
if isinstance(val, list):
val = [v.get(key, default) if v else None for v in val]
else:
val = val.get(key, default)
else:
val = dict.get(self, key, default)
if not val:
break
return val
Например:
person = {'person':{'name':{'first':'John'}}}
FindDict(person).get('person.name.first') # == 'John'
Если ключ не существует, он возвращает None
по умолчанию. Вы можете переопределить это, используя ключ default=
в обертке FindDict
- например `:
FindDict(person, default='').get('person.name.last') # == doesn't exist, so ''
После просмотра этого для глубокого получения атрибутов я сделал следующее, чтобы безопасно получить вложенные значения dict
, используя точечную нотацию. Это работает для меня, потому что мои dicts
являются десериализованными объектами MongoDB, поэтому я знаю, что имена ключей не содержат .
s. Кроме того, в моем контексте я могу указать значение ложного возврата (None
), которое у меня нет в моих данных, поэтому я могу избежать шаблона try/except при вызове функции.
from functools import reduce # Python 3
def deepgetitem(obj, item, fallback=None):
"""Steps through an item chain to get the ultimate value.
If ultimate value or path to value does not exist, does not raise
an exception and instead returns `fallback`.
>>> d = {'snl_final': {'about': {'_icsd': {'icsd_id': 1}}}}
>>> deepgetitem(d, 'snl_final.about._icsd.icsd_id')
1
>>> deepgetitem(d, 'snl_final.about._sandbox.sbx_id')
>>>
"""
def getitem(obj, name):
try:
return obj[name]
except (KeyError, TypeError):
return fallback
return reduce(getitem, item.split('.'), obj)
Адаптация ответа unutbu, который я нашел полезным в своем собственном коде:
example_dict.setdefaut('key1', {}).get('key2')
Он генерирует словарную запись для key1, если у него еще нет этого ключа, чтобы вы избежали KeyError. Если вы хотите в конечном итоге создать словарь, включающий такое сочетание клавиш, как я, это кажется самым простым решением.
Поскольку создание ключевой ошибки, если один из ключей отсутствует, является разумной вещью, мы можем даже не проверить ее и получить ее как единственную:
def get_dict(d, kl):
cur = d[kl[0]]
return get_dict(cur, kl[1:]) if len(kl) > 1 else cur