У меня есть вложенный словарь. Есть ли только один способ безопасно доставить ценности?
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 (или dict-подобным объектом с методом get
). Опубликованный вами код try..except
вызовет TypeError
, если example_dict['key1']
не подлежит подписке.
Другое отличие состоит в том, что 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
следующим образом:
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')
safeget
во многих отношениях небезопасен, поскольку он перезаписывает исходный словарь, а это означает, что вы не можете безопасно выполнять такие вещи, как safeget(dct, 'a', 'b') or safeget(dct, 'a')
.
- person neverfox; 11.04.2018
safeget
никогда не перезаписывает исходный словарь. Он либо вернет исходный словарь, либо значение из исходного словаря, либо None
.
- person unutbu; 12.04.2018
dct
в цикле for не приводит к перезаписи аргумента dct
(который является изменяемым словарем)? Это работает, но я не могу понять почему.
- person Kurt Bourbaki; 08.06.2018
dct = dct[key]
переназначает новое значение локальной переменной dct
. Это не изменяет исходный dict (поэтому safeget
не влияет на исходный dict). Если бы, с другой стороны, использовался dct[key] = ...
, то исходный dict был бы изменен. Другими словами, в Python имена привязаны к значениям. Присвоение нового значения имени не влияет на старое значение (если больше нет ссылок на старое значение, и в этом случае (в CPython) он будет собирать мусор.)
- person unutbu; 08.06.2018
{ 'key1': [ { 'key2' : 'catchme' }, ] }
вы не попробуете это
- person ChandraKumar; 11.04.2020
safeget
также не сработает, если ключ вложенного dict существует, но значение равно нулю. На следующей итерации будет выброшено TypeError: 'NoneType' object is not subscriptable
- person Stanley F.; 02.07.2020
{}
) вместо None?
- person mjkrause; 26.03.2021
with suppress(KeyError):
. См. Этот ответ: stackoverflow.com/a/45874251/1189659
- person IODEV; 14.04.2021
Вы также можете использовать 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
или чего-то еще.
- person stackunderflow; 27.02.2019
None
на Raise KeyError
- person Yuda Prawira; 15.08.2019
Опираясь на ответ Йоава, можно сделать еще более безопасный подход:
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]')
- person Fabio Caccamo; 31.01.2020
Простой класс, который может оборачивать словарный запас и извлекать его на основе ключа:
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')
Я адаптировал ответ GenesRus и unutbu в этом очень простом виде:
class new_dict(dict):
def deep_get(self, *args, default=None):
_empty_dict = {}
out = self
for key in args:
out = out.get(key, _empty_dict)
return out if out else default
он работает с: d = new_dict (some_data) d.deep_get (key1, key2, key3, ..., по умолчанию = some_value)
Увидев this для глубокого получения атрибутов, Я сделал следующее, чтобы безопасно получить вложенные значения 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
фактически не используется в функции.
- person 153957; 27.09.2016
.
- person JW.; 08.07.2017
sep=','
arg для обобщения для заданных условий (sep, fallback). И @denvar, если obj
относится к типу int
после последовательности сокращения, тогда obj [name] вызывает TypeError, которую я улавливаю. Если бы я использовал вместо этого obj.get (name) или obj.get (name, fallback), это вызвало бы ошибку AttributeError, так что в любом случае мне нужно было бы поймать.
- person Donny Winston; 17.12.2017
Вы можете использовать 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', ''))
(Правда, 'холла')
(Ложь, '')
Уже есть много хороших ответов, но я придумал a функция 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
glom
- хорошая библиотека, которая также может обрабатывать запросы с точками:
In [1]: from glom import glom
In [2]: data = {'a': {'b': {'c': 'd'}}}
In [3]: glom(data, "a.b.c")
Out[3]: 'd'
Ошибка запроса имеет хорошую трассировку стека, указывающую точное место сбоя:
In [4]: glom(data, "a.b.foo")
---------------------------------------------------------------------------
PathAccessError Traceback (most recent call last)
<ipython-input-4-2a3467493ac4> in <module>
----> 1 glom(data, "a.b.foo")
~/.cache/pypoetry/virtualenvs/neural-knapsack-dE7ihQtM-py3.8/lib/python3.8/site-packages/glom/core.py in glom(target, spec, **kwargs)
2179
2180 if err:
-> 2181 raise err
2182 return ret
2183
PathAccessError: error raised while processing, details below.
Target-spec trace (most recent last):
- Target: {'a': {'b': {'c': 'd'}}}
- Spec: 'a.b.foo'
glom.core.PathAccessError: could not access 'foo', part 2 of Path('a', 'b', 'foo'), got error: KeyError('foo')
Гарантия с default
:
In [5]: glom(data, "a.b.foo", default="spam")
Out[5]: 'spam'
Красота glom
в универсальном параметре спецификации. Например, можно легко извлечь все имена из следующих data
:
In [8]: data = {
...: "people": [
...: {"first_name": "Alice", "last_name": "Adams"},
...: {"first_name": "Bob", "last_name": "Barker"}
...: ]
...: }
In [9]: glom(data, ("people", ["first_name"]))
Out[9]: ['Alice', 'Bob']
Дополнительные примеры см. В glom
docs.
Начиная с Python 3.4, вы можете использовать with suppress (KeyError)
для доступа к вложенным json-объектам, не беспокоясь о Keyerror.
from contextlib import suppress
with suppress(KeyError):
a1 = json_obj['key1']['key2']['key3']
a2 = json_obj['key4']['key5']['key6']
a3 = json_obj['key7']['key8']['key9']
Любезно предоставлено Techdragon. Для получения дополнительных сведений ознакомьтесь с его ответом: https://stackoverflow.com/a/45874251/1189659
key1
отсутствует, переменная a1
не будет установлена вообще, что приведет к NameError
при попытке ее использования.
- person kolypto; 19.07.2021
Адаптация ответа 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)
Я использовал решение, подобное двойному get, но с дополнительной возможностью избежать TypeError с помощью логики if else:
value = example_dict['key1']['key2'] if example_dict.get('key1') and example_dict['key1'].get('key2') else default_value
Однако чем больше вложен словарь, тем громоздче он становится.
Для поиска по вложенному словарю / JSON вы можете использовать dictor
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)
Рекурсивный метод (мб пригодится)
Пример dict:
foo = [{'feature_name': 'Sample Creator > Contract Details > Elements of the page',
'scenarios': [{'scenario_name': 'SC, CD, Elements of the page',
'scenario_status': 'failed',
'scenario_tags': None,
'steps': [{'duration': 0,
'name': 'I open application Stage and login by '
'SPT_LOGIN and password SPT_PWD',
'status': 'untested'},
{'duration': 0,
'name': 'I open Sample Creator query page',
'status': 'untested'},
{'duration': 7.78166389465332,
'name': 'I open application Stage and login by '
'SPT_LOGIN and password SPT_PWD',
'status': 'passed'},
{'duration': 3.985326051712036,
'name': 'I open Sample Creator query page',
'status': 'passed'},
{'duration': 2.9063704013824463,
'name': 'Enter value: '
'X-2008-CON-007,X-2011-CON-016 in '
'textarea: project_text_area sleep: 1',
'status': 'passed'},
{'duration': 4.4447715282440186,
'name': 'I press on GET DATA',
'status': 'passed'},
{'duration': 1.1209557056427002,
'name': 'Verify the top table on Contract Details',
'status': 'passed'},
{'duration': 3.8173601627349854,
'name': 'I export contract_details table by offset '
'x:100, y:150',
'status': 'passed'},
{'duration': 1.032956600189209,
'name': 'Check data of '
'sc__cd_elements_of_the_page_1 and skip '
'cols None',
'status': 'passed'},
{'duration': 0.04593634605407715,
'name': "Verify 'Number of Substances' column "
'values',
'status': 'passed'},
{'duration': 0.10199904441833496,
'name': 'Substance Sample Details bottom table '
'columns',
'status': 'passed'},
{'duration': 0.0009999275207519531,
'name': 'Verify the Substance Sample Details '
'bottom table',
'status': 'passed'},
{'duration': 3.8558616638183594,
'name': 'I export substance_sample_details table '
'by offset x:100, y:150',
'status': 'passed'},
{'duration': 1.0329277515411377,
'name': 'Check data of '
'sc__cd_elements_of_the_page_2 and skip '
'cols None',
'status': 'passed'},
{'duration': 0.2879970073699951,
'name': 'Click on AG-13369',
'status': 'passed'},
{'duration': 3.800830364227295,
'name': 'I export substance_sample_details table '
'by offset x:100, y:150',
'status': 'passed'},
{'duration': 1.0169551372528076,
'name': 'Check data of '
'sc__cd_elements_of_the_page_3 and skip '
'cols None',
'status': 'passed'},
{'duration': 1.7484464645385742,
'name': 'Select all cells, table: 2',
'status': 'passed'},
{'duration': 3.812828779220581,
'name': 'I export substance_sample_details table '
'by offset x:100, y:150',
'status': 'passed'},
{'duration': 1.0029594898223877,
'name': 'Check data of '
'sc__cd_elements_of_the_page_2 and skip '
'cols None',
'status': 'passed'},
{'duration': 1.6729373931884766,
'name': 'Set window size x:800, y:600',
'status': 'passed'},
{'duration': 30.145705699920654,
'name': 'All scrollers are placed on top 6 and far '
'left 8',
'status': 'failed'}]}]},
{'feature_name': 'Sample Creator > Substance Sample History > Elements of the '
'page',
'scenarios': [{'scenario_name': 'SC, SSH, Elements of the page',
'scenario_status': 'passed',
'scenario_tags': None,
'steps': [{'duration': 0,
'name': 'I open application Stage and login by '
'SPT_LOGIN and password SPT_PWD',
'status': 'untested'},
{'duration': 0,
'name': 'I open Sample Creator query page',
'status': 'untested'},
{'duration': 7.305850505828857,
'name': 'I open application Stage and login by '
'SPT_LOGIN and password SPT_PWD',
'status': 'passed'},
{'duration': 3.500955104827881,
'name': 'I open Sample Creator query page',
'status': 'passed'},
{'duration': 3.0419492721557617,
'name': 'Enter value: NOA401800 SYN-NOA '
'A,S4A482070C SYN-ISN-OLD '
'O,S04A482167T,S04A482190Y,CSAA796564,CSCD106701 '
'in textarea: id_text_area sleep: 1',
'status': 'passed'},
{'duration': 49.567158460617065,
'name': 'I press on GET DATA',
'status': 'passed'},
{'duration': 0.13904356956481934,
'name': 'Open substance_sample_history',
'status': 'passed'},
{'duration': 1.1039845943450928,
'name': 'Columns displayed',
'status': 'passed'},
{'duration': 3.881945848464966,
'name': 'I export export_parent_table table by '
'offset x:100, y:150',
'status': 'passed'},
{'duration': 1.0334820747375488,
'name': 'Check data of '
'sc__ssh_elements_of_the_page_1 and skip '
'cols None',
'status': 'passed'},
{'duration': 0.0319981575012207,
'name': "Title is 'Additional Details for Marked "
"Rows'",
'status': 'passed'},
{'duration': 0.08897256851196289,
'name': 'Columns displayed (the same as in top '
'table)',
'status': 'passed'},
{'duration': 25.192569971084595,
'name': 'Verify the content of the bottom table',
'status': 'passed'},
{'duration': 4.308935880661011,
'name': 'I export '
'additional_details_for_marked_rows table '
'by offset x:100, y:150',
'status': 'passed'},
{'duration': 1.0089836120605469,
'name': 'Check data of '
'sc__ssh_elements_of_the_page_1 and skip '
'cols None',
'status': 'passed'}]}]}]
Код:
def get_keys(_dict: dict, prefix: list):
prefix += list(_dict.keys())
return prefix
def _loop_elements(elems:list, prefix=None, limit=None):
prefix = prefix or []
limit = limit or 9
try:
if len(elems) != 0 and isinstance(elems, list):
for _ in elems:
if isinstance(_, dict):
get_keys(_, prefix)
for item in _.values():
_loop_elements(item, prefix, limit)
return prefix[:limit]
except TypeError:
return
>>>goo = _loop_elements(foo,limit=9)
>>>goo
['feature_name', 'scenarios', 'scenario_name', 'scenario_status', 'scenario_tags', 'steps', 'duration', 'name', 'status']
Я написал пакет deepextract, который делает именно то, что вы хотите: https://github.com/ya332/deepextract Вы можете сделать
from deepextract import deepextract
# Demo: deepextract.extract_key(obj, key)
deeply_nested_dict = {
"items": {
"item": {
"id": {
"type": {
"donut": {
"name": {
"batters": {
"my_target_key": "my_target_value"
}
}
}
}
}
}
}
}
print(deepextract.extract_key(deeply_nested_dict, "my_target_key") == "my_target_value")
возвращается
True
Моя реализация, которая спускается в подкаталоги, игнорирует значения None
, но терпит неудачу с ошибкой TypeError, если обнаруживается что-то еще
def deep_get(d: dict, *keys, default=None):
""" Safely get a nested value from a dict
Example:
config = {'device': None}
deep_get(config, 'device', 'settings', 'light')
# -> None
Example:
config = {'device': True}
deep_get(config, 'device', 'settings', 'light')
# -> TypeError
Example:
config = {'device': {'settings': {'light': 'bright'}}}
deep_get(config, 'device', 'settings', 'light')
# -> 'light'
Note that it returns `default` is a key is missing or when it's None.
It will raise a TypeError if a value is anything else but a dict or None.
Args:
d: The dict to descend into
keys: A sequence of keys to follow
default: Custom default value
"""
# Descend while we can
try:
for k in keys:
d = d[k]
# If at any step a key is missing, return default
except KeyError:
return default
# If at any step the value is not a dict...
except TypeError:
# ... if it's a None, return default. Assume it would be a dict.
if d is None:
return default
# ... if it's something else, raise
else:
raise
# If the value was found, return it
else:
return d
except keyerror:
. - person Peter Schorn   schedule 16.02.2020