Безопасный метод получения значения вложенного словаря

У меня есть вложенный словарь. Есть ли только один способ безопасно доставить ценности?

try:
    example_dict['key1']['key2']
except KeyError:
    pass

Или, может быть, у python есть метод типа get() для вложенного словаря?


person Arti    schedule 14.09.2014    source источник
comment
См. также: stackoverflow.com/questions/14692690/   -  person dreftymac    schedule 18.01.2019
comment
Код в вашем вопросе, на мой взгляд, уже является лучшим способом получить вложенные значения из словаря. Вы всегда можете указать значение по умолчанию в предложении except keyerror:.   -  person Peter Schorn    schedule 16.02.2020


Ответы (25)


Вы можете использовать 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')
person unutbu    schedule 14.09.2014
comment
Итак, у python нет красивого решения для этого случая? :( - person Arti; 14.09.2014
comment
У меня возникла проблема с аналогичной реализацией. Если у вас есть d = {key1: None}, первое get вернет None, а затем у вас будет исключение): я пытаюсь найти решение для этого - person Huercio; 31.01.2018
comment
Метод safeget во многих отношениях небезопасен, поскольку он перезаписывает исходный словарь, а это означает, что вы не можете безопасно выполнять такие вещи, как safeget(dct, 'a', 'b') or safeget(dct, 'a'). - person neverfox; 11.04.2018
comment
safeget никогда не перезаписывает исходный словарь. Он либо вернет исходный словарь, либо значение из исходного словаря, либо None. - person unutbu; 12.04.2018
comment
@unutbu, не могли бы вы объяснить, почему перезапись значения dct в цикле for не приводит к перезаписи аргумента dct (который является изменяемым словарем)? Это работает, но я не могу понять почему. - person Kurt Bourbaki; 08.06.2018
comment
@KurtBourbaki: dct = dct[key] переназначает новое значение локальной переменной dct. Это не изменяет исходный dict (поэтому safeget не влияет на исходный dict). Если бы, с другой стороны, использовался dct[key] = ..., то исходный dict был бы изменен. Другими словами, в Python имена привязаны к значениям. Присвоение нового значения имени не влияет на старое значение (если больше нет ссылок на старое значение, и в этом случае (в CPython) он будет собирать мусор.) - person unutbu; 08.06.2018
comment
А как насчет того, чтобы взять копию dict в начале метода safeget? (используя copy.deepcopy) - person Martin Alexandersson; 10.08.2018
comment
Пока хорошо, если { 'key1': [ { 'key2' : 'catchme' }, ] } вы не попробуете это - person ChandraKumar; 11.04.2020
comment
Метод safeget также не сработает, если ключ вложенного dict существует, но значение равно нулю. На следующей итерации будет выброшено TypeError: 'NoneType' object is not subscriptable - person Stanley F.; 02.07.2020
comment
Мне нравится метод safeget! В духе того, что метод должен возвращать только один тип данных, как насчет возврата пустого словаря ({}) вместо None? - person mjkrause; 26.03.2021
comment
Начиная с Python 3.4 вы можете использовать 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)
person Yoav T    schedule 21.03.2016
comment
Просто хотел упомянуть, что functools больше не встроен в Python3 и необходимо импортировать из functools, что делает этот подход немного менее элегантным. - person yoniLavi; 12.04.2018
comment
Небольшая поправка к этому комментарию: reduce больше не встроено в Py3. Но я не понимаю, почему это делает его менее элегантным. Это действительно делает его менее подходящим для однострочника, но однострочность не означает автоматически или дисквалифицирует что-то как элегантное. - person PaulMcG; 24.01.2020
comment
Обратите внимание, что использование try / except обычно лучше, поскольку оно не требует дополнительных вычислительных затрат для проверки действительности ключа. Поэтому, если в большинстве случаев ключи существуют, я бы порекомендовал попробовать / исключить парадигму для повышения эффективности. - person milembar; 12.11.2020

Я думаю, что эта функция будет полезна, объединив здесь все эти ответы и небольшие изменения, которые я внес. это безопасно, быстро, легко ремонтируется.

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
>>>
person Yuda Prawira    schedule 23.10.2017
comment
Идеально подходит для шаблонов Jinja2 - person Thomas; 09.03.2018
comment
Это хорошее решение, хотя есть и недостаток: даже если первый ключ недоступен или значение, переданное в качестве аргумента словаря функции, не является словарем, функция перейдет от первого элемента к последнему. По сути, так происходит во всех случаях. - person Arseny; 28.01.2019
comment
deep_get({'a': 1}, "a.b") дает None, но я ожидал бы исключения типа KeyError или чего-то еще. - person stackunderflow; 27.02.2019
comment
@edityouprofile. тогда вам просто нужно сделать небольшое изменение, чтобы изменить возвращаемое значение с None на Raise KeyError - person Yuda Prawira; 15.08.2019
comment
Это лучший ответ! - person Joel; 20.07.2021

Опираясь на ответ Йоава, можно сделать еще более безопасный подход:

def deep_get(dictionary, *keys):
    return reduce(lambda d, key: d.get(key, None) if isinstance(d, dict) else None, keys, dictionary)
person Jose Alban    schedule 18.11.2016

Рекурсивное решение. Это не самый эффективный вариант, но я считаю его более читаемым, чем другие примеры, и он не полагается на функциональные модули.

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)
person Pithikos    schedule 04.05.2018

Хотя метод сокращения понятен и короток, я думаю, что простой цикл легче понять. Я также включил параметр по умолчанию.

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')
person zzz    schedule 25.04.2017
comment
Мне нравится петля, потому что она более гибкая. Например, его можно использовать для применения некоторой лямбды к вложенному полю, а не для ее получения. - person milembar; 12.11.2020

Предлагаю попробовать 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

Примечание: я являюсь автором этого проекта

person Fabio Caccamo    schedule 29.10.2019
comment
@ perfecto25 спасибо! Скоро я опубликую новые функции, следите за обновлениями ???? - person Fabio Caccamo; 18.01.2020
comment
@ perfecto25 Я добавил поддержку индексов списков, например. d.get('a.b[0].c[-1]') - person Fabio Caccamo; 31.01.2020
comment
Функция from_toml, похоже, не реализована. И может быть сложно импортировать BeneDict. - person DLyons; 30.07.2020
comment
@DLyons, вы ошибаетесь, в любом случае не стесняйтесь открывать вопрос на GitHub. - person Fabio Caccamo; 30.07.2020
comment
Да все в порядке. Жалко, что я это пропустил - сэкономило бы мне время. Бенедикт, кажется, обладает некоторыми очень полезными функциями. - person DLyons; 31.07.2020
comment
@DLyons спасибо, если у вас есть идеи о новых функциях, просто дайте мне знать на GitHub. - person Fabio Caccamo; 31.07.2020
comment
Бенедикту казалось, что стихи нравятся ему. Но любые варианты из Benedict import, которые мне дал Бенедикт, не могут импортировать ошибку BeneDict. Я попробовал несколько вещей, например, удалить все из init .py, но, в конце концов, мне показалось, что в противном случае быстрее загрузить и проанализировать мой небольшой обычный файл toml. - person DLyons; 31.07.2020
comment
Просто откройте проблему здесь: github.com/fabiocaccamo/python-benedict/issues - person Fabio Caccamo; 31.07.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 ''
person Lee Benson    schedule 01.07.2017

для получения ключа второго уровня вы можете сделать это:

key2_value = (example_dict.get('key1') or {}).get('key2')
person Jacob CUI    schedule 30.06.2017

Я адаптировал ответ 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)

person n4321d    schedule 19.03.2021

Увидев 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)
person Donny Winston    schedule 27.07.2016
comment
fallback фактически не используется в функции. - person 153957; 27.09.2016
comment
Обратите внимание, что это не работает для ключей, содержащих . - person JW.; 08.07.2017
comment
Когда мы вызываем obj [name], почему бы не использовать obj.get (name, fallback) и избегать try-catch (если вы действительно хотите try-catch, то возвращайте резервный вариант, а не None) - person denvar; 15.12.2017
comment
Спасибо @ 153957. Я починил это. И да, @JW, это работает для моего варианта использования. Вы можете добавить ключевое слово 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

person Santi Magariños    schedule 20.05.2020

Еще одна функция для того же самого, также возвращает логическое значение, чтобы показать, был ли ключ найден или нет, и обрабатывает некоторые неожиданные ошибки.

'''
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', ''))

(Правда, 'холла')

(Ложь, '')

person penduDev    schedule 05.12.2019

Уже есть много хороших ответов, но я придумал 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
person Peter Marklund    schedule 11.07.2020
comment
единственный, который проходит мои тесты со списками :) - person Igor L.; 29.10.2020

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.

person hoefling    schedule 28.01.2021

Начиная с 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

person IODEV    schedule 14.04.2021
comment
Обратите внимание, что если key1 отсутствует, переменная a1 не будет установлена ​​вообще, что приведет к NameError при попытке ее использования. - person kolypto; 19.07.2021

Адаптация ответа unutbu, который я нашел полезным в моем собственном коде:

example_dict.setdefaut('key1', {}).get('key2')

Он генерирует словарную запись для key1, если у него еще нет этого ключа, чтобы вы избежали KeyError. Если вы хотите получить вложенный словарь, который все равно включает эту пару ключей, как это сделал я, это кажется самым простым решением.

person GenesRus    schedule 05.04.2018

Поскольку создание ключевой ошибки при отсутствии одного из ключей - разумная вещь, мы можем даже не проверять ее и получить такую ​​единственную ошибку:

def get_dict(d, kl):
  cur = d[kl[0]]
  return get_dict(cur, kl[1:]) if len(kl) > 1 else cur
person Ben Usman    schedule 17.08.2018

Небольшое улучшение 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)
person Mike Kor    schedule 01.11.2019

Я использовал решение, подобное двойному get, но с дополнительной возможностью избежать TypeError с помощью логики if else:

    value = example_dict['key1']['key2'] if example_dict.get('key1') and example_dict['key1'].get('key2') else default_value

Однако чем больше вложен словарь, тем громоздче он становится.

person Adam The Housman    schedule 13.12.2019

Для поиска по вложенному словарю / 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

person perfecto25    schedule 17.01.2020

Я немного изменил этот ответ. Я добавил проверку, используем ли мы список с числами. Итак, теперь мы можем использовать его как угодно. 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)
person Darex1991    schedule 19.03.2020
comment
не работает: test_dict = {a: {b: [{c: value}]}} self.assertEqual (safeget (test_dict, [a, b, 1, c], None) - person Igor L.; 29.10.2020

Рекурсивный метод (мб пригодится)

Пример 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']
person Igor Z    schedule 20.11.2020

Я написал пакет 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
person Yigit Alparslan    schedule 03.03.2021

Моя реализация, которая спускается в подкаталоги, игнорирует значения 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
person kolypto    schedule 19.07.2021