11 ответов:
вы могли бы использовать
getдважды:example_dict.get('key1', {}).get('key2')возвращает
Noneеслиkey1илиkey2не существует.обратите внимание, что это все еще может вызвать
AttributeErrorеслиexample_dict['key1']существует, но не является dict (или dict-подобный объект сgetметод). Элементtry..exceptкод, который вы выложили бы поднятьTypeErrorвместоexample_dict['key1']отписаться невозможно.еще одно отличие заключается в том, что
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вы можете использовать Хэшер во многом так же, как вы могли бы использовать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)
в то время как сократить подход аккуратный и короткий, я думаю, что простой цикл легче Грок. Я также включил параметр по умолчанию.
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')
простой класс, который может обернуть 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 ''
объединив все эти ответы здесь и небольшие изменения, которые я сделал, я думаю, эта функция будет полезна. его безопасный, быстрый, легко ремонтопригодный.
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
Comments