У меня есть вложенный словарь. Есть ли только один способ безопасно доставить ценности?
try:
example_dict['key1']['key2']
except KeyError:
pass
Или, может быть, у python есть такой метод, как get()
вложенный словарь?
У меня есть вложенный словарь. Есть ли только один способ безопасно доставить ценности?
try:
example_dict['key1']['key2']
except KeyError:
pass
Или, может быть, у python есть такой метод, как get()
вложенный словарь?
except keyerror:
.Ответы:
Вы можете использовать
get
дважды:example_dict.get('key1', {}).get('key2')
Это вернется,
None
если один из нихkey1
илиkey2
не существует.Обратите внимание, что это все еще может вызвать
AttributeError
if,example_dict['key1']
но не является dict (или dict-подобным объектом сget
методом).try..except
Код отвечал бы приподнятьTypeError
вместо этого , еслиexample_dict['key1']
это unsubscriptable.Еще одно отличие состоит в том, что происходит
try...except
короткое замыкание сразу после первого недостающего ключа. Цепочкиget
звонков нет.Если вы хотите сохранить синтаксис,
example_dict['key1']['key2']
но не хотите, чтобы он когда-либо вызывал KeyErrors, вы можете использовать рецепт Hasher :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
такой:и так же легко преобразовать a
Hasher
в обычныйdict
:Другая альтернатива - скрыть уродство во вспомогательной функции:
def safeget(dct, *keys): for key in keys: try: dct = dct[key] except KeyError: return None return dct
Таким образом, остальная часть вашего кода может оставаться относительно читаемой:
safeget(example_dict, 'key1', 'key2')
источник
safeget
метод во многих отношениях не очень безопасен, поскольку он перезаписывает исходный словарь, а это означает, что вы не можете безопасно делать такие вещи, какsafeget(dct, 'a', 'b') or safeget(dct, 'a')
.dct = dct[key]
переназначает новое значение локальной переменнойdct
. Это не изменяет исходный dict (поэтому исходный dict не изменяетсяsafeget
). Если бы, с другой стороны,dct[key] = ...
использовался, то исходный dict был бы изменен. Другими словами, в Python имена привязаны к значениям . Присвоение нового значения имени не влияет на старое значение (если больше нет ссылок на старое значение, и в этом случае (в CPython) он будет собирать мусор.)safeget
Метод также не будет в случае , если ключ вложенной Словаре существует, но значение равно нулю. Будет добавленаTypeError: 'NoneType' object is not subscriptable
следующая итерацияВы также можете использовать 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 >>>
источник
deep_get({'a': 1}, "a.b")
дает,None
но я ожидал бы исключения вродеKeyError
или чего-то еще.None
наRaise KeyError
Опираясь на ответ Йоава, можно сделать еще более безопасный подход:
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(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)
источник
Хотя метод сокращения понятен и краток, я думаю, что простой цикл легче понять. Я также включил параметр по умолчанию.
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')
источник
Предлагаю вам попробовать
python-benedict
.Это
dict
подкласс, который обеспечивает поддержку ключевых путей и многое другое.Установка:
pip install python-benedict
from benedict import benedict example_dict = benedict(example_dict, keypath_separator='.')
теперь вы можете получить доступ к вложенным значениям, используя keypath :
val = example_dict['key1.key2'] # using 'get' method to avoid a possible KeyError: val = example_dict.get('key1.key2')
или получить доступ к вложенным значениям с помощью списка ключей :
val = example_dict['key1', 'key2'] # using get to avoid a possible KeyError: val = example_dict.get(['key1', 'key2'])
Он хорошо протестирован и имеет открытый исходный код на GitHub:
https://github.com/fabiocaccamo/python-benedict
Примечание: я являюсь автором этого проекта
источник
d.get('a.b[0].c[-1]')
Простой класс, который может оборачивать словарный запас и извлекать его на основе ключа:
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 ''
источник
для получения ключа второго уровня вы можете сделать это:
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)
источник
fallback
фактически не используется в функции..
sep=','
ключевое слово arg для обобщения для заданных (sep, fallback) условий. И @denvar, еслиobj
это говорит о типеint
после последовательности сокращения, тогда obj [name] вызывает TypeError, которую я улавливаю. Если бы я использовал вместо этого obj.get (name) или obj.get (name, fallback), это вызвало бы AttributeError, так что в любом случае мне нужно было бы поймать.Вы можете использовать pydash:
import pydash as _ _.get(example_dict, 'key1.key2', default='Default')
https://pydash.readthedocs.io/en/latest/api.html
источник
Еще одна функция для того же самого, также возвращает логическое значение, чтобы указать, был ли ключ найден или нет, и обрабатывает некоторые неожиданные ошибки.
''' json : json to extract value from if exists path : details.detail.first_name empty path represents root returns a tuple (boolean, object) boolean : True if path exists, otherwise False object : the object if path exists otherwise None ''' def get_json_value_at_path(json, path=None, default=None): if not bool(path): return True, json if type(json) is not dict : raise ValueError(f'json={json}, path={path} not supported, json must be a dict') if type(path) is not str and type(path) is not list: raise ValueError(f'path format {path} not supported, path can be a list of strings like [x,y,z] or a string like x.y.z') if type(path) is str: path = path.strip('.').split('.') key = path[0] if key in json.keys(): return get_json_value_at_path(json[key], path[1:], default) else: return False, default
пример использования:
my_json = {'details' : {'first_name' : 'holla', 'last_name' : 'holla'}} print(get_json_value_at_path(my_json, 'details.first_name', '')) print(get_json_value_at_path(my_json, 'details.phone', ''))
источник
Уже есть много хороших ответов, но я придумал функцию под названием get, похожую на lodash get в области JavaScript, которая также поддерживает доступ к спискам по индексу:
def get(value, keys, default_value = None): ''' Useful for reaching into nested JSON like data Inspired by JavaScript lodash get and Clojure get-in etc. ''' if value is None or keys is None: return None path = keys.split('.') if isinstance(keys, str) else keys result = value def valid_index(key): return re.match('^([1-9][0-9]*|[0-9])$', key) and int(key) >= 0 def is_dict_like(v): return hasattr(v, '__getitem__') and hasattr(v, '__contains__') for key in path: if isinstance(result, list) and valid_index(key) and int(key) < len(result): result = result[int(key)] if int(key) < len(result) else None elif is_dict_like(result) and key in result: result = result[key] else: result = default_value break return result def test_get(): assert get(None, ['foo']) == None assert get({'foo': 1}, None) == None assert get(None, None) == None assert get({'foo': 1}, []) == {'foo': 1} assert get({'foo': 1}, ['foo']) == 1 assert get({'foo': 1}, ['bar']) == None assert get({'foo': 1}, ['bar'], 'the default') == 'the default' assert get({'foo': {'bar': 'hello'}}, ['foo', 'bar']) == 'hello' assert get({'foo': {'bar': 'hello'}}, 'foo.bar') == 'hello' assert get({'foo': [{'bar': 'hello'}]}, 'foo.0.bar') == 'hello' assert get({'foo': [{'bar': 'hello'}]}, 'foo.1') == None assert get({'foo': [{'bar': 'hello'}]}, 'foo.1.bar') == None assert get(['foo', 'bar'], '1') == 'bar' assert get(['foo', 'bar'], '2') == None
источник
Адаптация ответа 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
источник
Небольшое улучшение
reduce
подхода, чтобы заставить его работать со списком. Также используется путь к данным в виде строки, разделенной точками, вместо массива.def deep_get(dictionary, path): keys = path.split('.') return reduce(lambda d, key: d[int(key)] if isinstance(d, list) else d.get(key) if d else None, keys, dictionary)
источник
Решение, которое я использовал, похоже на двойное получение, но с дополнительной возможностью избежать TypeError с помощью логики if else:
value = example_dict['key1']['key2'] if example_dict.get('key1') and example_dict['key1'].get('key2') else default_value
Однако чем больше вложен словарь, тем громоздче он становится.
источник
Для вложенного словаря / поиска JSON вы можете использовать диктор
pip install dictor
объект dict
{ "characters": { "Lonestar": { "id": 55923, "role": "renegade", "items": [ "space winnebago", "leather jacket" ] }, "Barfolomew": { "id": 55924, "role": "mawg", "items": [ "peanut butter jar", "waggy tail" ] }, "Dark Helmet": { "id": 99999, "role": "Good is dumb", "items": [ "Shwartz", "helmet" ] }, "Skroob": { "id": 12345, "role": "Spaceballs CEO", "items": [ "luggage" ] } } }
чтобы получить предметы Lonestar, просто укажите путь, разделенный точками, т.е.
import json from dictor import dictor with open('test.json') as data: data = json.load(data) print dictor(data, 'characters.Lonestar.items') >> [u'space winnebago', u'leather jacket']
вы можете предоставить резервное значение, если ключ отсутствует в пути
Вы можете использовать множество других параметров, например игнорировать регистр букв и использовать другие символы, кроме '.' как разделитель путей,
https://github.com/perfecto25/dictor
источник
Я немного изменил этот ответ. Я добавил проверку, используем ли мы список с числами. Итак, теперь мы можем использовать его как угодно.
deep_get(allTemp, [0], {})
или иdeep_get(getMinimalTemp, [0, minimalTemperatureKey], 26)
т. д.def deep_get(_dict, keys, default=None): def _reducer(d, key): if isinstance(d, dict): return d.get(key, default) if isinstance(d, list): return d[key] if len(d) > 0 else default return default return reduce(_reducer, keys, _dict)
источник