Безопасный метод Python для получения значения вложенного словаря
у меня есть вложенные словарь. Есть ли только один способ безопасно получить ценности?
try:
example_dict['key1']['key2']
except KeyError:
pass
или, может быть, python имеет такой метод, как get()
для вложенных словарь ?
11 ответов
вы могли бы использовать get
дважды:
example_dict.get('key1', {}).get('key2')
возвращает None
если key1
или key2
не существует.
обратите внимание, что это еще может поднять AttributeError
если example_dict['key1']
существует, но не является dict (или dict-подобным объектом с get
метод). The try..except
код, который вы разместили, поднимет TypeError
вместо if example_dict['key1']
это unsubscriptable.
другое различие заключается в том, что try...except
короткого замыкания сразу после первого пропал ключ. Цепь get
звонки не.
если вы хотите сохранить синтаксис example_dict['key1']['key2']
но не хочу, чтобы он когда-либо поднимал KeyErrors, тогда вы могли бы использовать рецепт домработника:
class Hasher(dict):
# https://stackoverflow.com/a/3405143/190597
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
является наследником dict
вы можете использовать Hasher почти так же, как вы могли бы использовать dict
. Все те же методы и синтаксис доступен, Хешеры просто относятся к отсутствующим ключам по-разному.
вы можете конвертировать обычный dict
на Hasher
такой:
hasher = Hasher(example_dict)
и преобразовать 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 уменьшить:
def deep_get(dictionary, *keys):
return reduce(lambda d, key: d.get(key) if d else None, keys, dictionary)
построение ответа Йоава, еще более безопасный подход:
def deep_get(dictionary, *keys):
return reduce(lambda d, key: d.get(key, None) if isinstance(d, dict) else None, keys, dictionary)
В то время как подход сокращения аккуратный и короткий, я думаю, что простой цикл легче grok. Я также включил параметр по умолчанию.
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
в качестве упражнения, чтобы понять, как работает reduce one-liner, я сделал следующее. Но в конечном счете петлевой подход кажется мне более интуитивным.
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')
простой класс, который может обернуть дикт, и получить на основе ключа:
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 ''
объединив все эти ответы здесь и небольшие изменения, которые я сделал, я думаю, эта функция будет полезна. его безопасный, быстрый, легко ремонтируемый.
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
>>>
для получения ключа второго уровня вы можете сделать следующее:
key2_value = (example_dict.get('key1') or {}).get('key2')
увидев этой для глубокого получения атрибутов я сделал следующее, чтобы безопасно получить вложенные 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)
рекурсивное решение. Это не самый эффективный, но я нахожу его немного более читаемым, чем другие примеры, и он не полагается на функции.
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)
адаптация ответа 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