【问题标题】:Safe method to get value of nested dictionary获取嵌套字典值的安全方法
【发布时间】:2014-11-08 03:03:54
【问题描述】:

我有一个嵌套字典。只有一种方法可以安全地获取价值吗?

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

或者也许 python 有一个类似get() 的方法用于嵌套字典?

【问题讨论】:

  • 在我看来,您问题中的代码已经是从字典中获取嵌套值的最佳方法。您始终可以在 except keyerror: 子句中指定默认值。

标签: python dictionary methods except


【解决方案1】:

你可以使用get 两次:

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

如果key1key2 不存在,这将返回None

请注意,如果example_dict['key1'] 存在但不是字典(或具有get 方法的类似字典的对象),这仍然会引发AttributeError。如果example_dict['key1'] 不可订阅,您发布的try..except 代码将引发TypeError

另一个区别是try...except 在第一个丢失的密钥之后立即短路。 get 调用链没有。


如果您希望保留语法 example_dict['key1']['key2'] 但不希望它引发 KeyErrors,那么您可以使用 Hasher recipe

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

请注意,当缺少键时,这会返回一个空的哈希器。

由于Hasherdict 的子类,因此您可以像使用dict 一样使用Hasher。所有相同的方法和语法都可用,Hashers 只是以不同的方式处理丢失的键。

您可以像这样将普通的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 没有漂亮的解决方案?:(
  • safeget 方法在很多方面都不是很安全,因为它会覆盖原始字典,这意味着您不能安全地执行 safeget(dct, 'a', 'b') or safeget(dct, 'a') 之类的操作。
  • @KurtBourbaki:dct = dct[key]重新分配一个新值给局部变量dct。这不会改变原始字典(因此原始字典不受safeget 的影响。)另一方面,如果使用了dct[key] = ...,那么原始字典将被修改。换句话说,在 Python 中names are bound to values。为名称分配新值不会影响旧值(除非不再引用旧值,在这种情况下(在 CPython 中)它将被垃圾收集。)
  • safeget 方法也会在嵌套 dict 的键存在但值为 null 的情况下失败。它将在下一次迭代中抛出TypeError: 'NoneType' object is not subscriptable
  • 从 Python 3.4 开始,您可以使用 with suppress(KeyError):。看到这个答案:stackoverflow.com/a/45874251/1189659
【解决方案2】:

你也可以使用 python reduce:

def deep_get(dictionary, *keys):
    return reduce(lambda d, key: d.get(key) if d else None, keys, dictionary)

【讨论】:

  • 只想提一下,functools 是no longer a builtin in Python3,需要从functools 导入,这使得这种方式略显不优雅。
  • 对此评论稍作修正:reduce 不再是 Py3 的内置功能。但我不明白为什么这会让它变得不那么优雅。它确实使其不太适合单线,但作为单线并不会自动将某物视为“优雅”。
  • 请注意,使用 try/except 通常会更好,因为它不涉及检查密钥有效性的额外计算负担。因此,如果大多数情况下密钥都存在,那么我建议使用 try/except 范式来提高效率。
【解决方案3】:

通过结合所有这些答案和我所做的小改动,我认为这个功能会很有用。它安全、快速、易于维护。

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
>>>

【讨论】:

  • 非常适合 Jinja2 模板
  • 这是一个很好的解决方案,但也有一个缺点:即使第一个键不可用,或者作为字典参数传递给函数的值不是字典,函数也会从第一个元素到最后一个元素。基本上,它在所有情况下都会这样做。
  • deep_get({'a': 1}, "a.b") 给出了None,但我希望有一个像KeyError 这样的异常。
  • @edityouprofile。那么你只需要做一些小的修改,将返回值从None 更改为Raise KeyError
  • 这是最好的答案!
【解决方案4】:

以 Yoav 的回答为基础,一种更安全的方法:

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

【讨论】:

    【解决方案5】:

    递归解决方案。它不是最有效的,但我发现它比其他示例更具可读性,并且它不依赖于 functools。

    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)
    

    【讨论】:

      【解决方案6】:

      虽然 reduce 方法简洁而简洁,但我认为简单的循环更容易理解。我还包含了一个默认参数。

      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 单行如何工作的练习,我做了以下操作。但最终循环方法对我来说似乎更直观。

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

      【讨论】:

      • 我喜欢循环,因为它更灵活。例如,可以使用它在嵌套字段上应用一些 lambda,而不是获取它。
      【解决方案7】:

      我建议你试试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

      注意:我是这个项目的作者

      【讨论】:

      • @perfecto25 谢谢!我会尽快发布新功能,敬请期待?
      • @perfecto25 我添加了对列表索引的支持,例如。 d.get('a.b[0].c[-1]')
      • from_toml 函数好像没有实现。而且可能很难导入 BeneDict。
      • @DLyons 你错了,在任何情况下都可以在 GitHub 上打开一个问题。
      • 是的,没问题。可惜我错过了 - 可以节省我一些时间。 Benedict 似乎有一些非常有用的功能。
      【解决方案8】:

      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 了解更多示例。

      【讨论】:

        【解决方案9】:

        在第一阶段,您可以 .get 一个空字典。

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

        【讨论】:

          【解决方案10】:

          一个简单的类,可以包装一个字典,并根据一个键检索:

          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'
          

          如果key不存在,默认返回None。您可以使用 FindDict 包装器中的 default= 键覆盖它——例如`:

          FindDict(person, default='').get('person.name.last') # == doesn't exist, so ''
          

          【讨论】:

            【解决方案11】:

            我用这个非常简单的方式改编了 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", ..., default=some_value)

            【讨论】:

              【解决方案12】:

              对于二级密钥检索,您可以这样做:

              key2_value = (example_dict.get('key1') or {}).get('key2')
              

              【讨论】:

                【解决方案13】:

                你可以使用 pydash:

                import pydash as _  #NOTE require `pip install pydash`
                
                _.get(example_dict, 'key1.key2', default='Default')
                

                https://pydash.readthedocs.io/en/latest/api.html

                【讨论】:

                • 这在我看来一定是公认的答案!
                【解决方案14】:

                在看到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 实际上并没有在函数中使用。
                • 请注意,这不适用于包含 . 的键
                • 当我们调用 obj[name] 为什么不调用 obj.get(name, fallback) 并避免 try-catch(如果你确实想要 try-catch,则返回 fallback,而不是 None)跨度>
                • 谢谢@153957。我修好了它。是的@JW,这适用于我的用例。您可以添加 sep=',' 关键字 arg 以概括给定(sep,后备)条件。而@denvar,如果obj 在一系列reduce 之后是int 类型,那么obj[name] 会引发一个TypeError,我会抓住它。如果我改用 obj.get(name) 或 obj.get(name, fallback),它会引发 AttributeError,所以无论哪种方式我都需要捕获。
                【解决方案15】:

                从 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
                【解决方案16】:

                同样的另一个函数,也返回一个布尔值来表示是否找到密钥并处理一些意外错误。

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

                (真的,'holla')

                (假,'')

                【讨论】:

                  【解决方案17】:

                  已经有很多好的答案,但我想出了a function called get,类似于 JavaScript 领域的 lodash get,它也支持按索引进入列表:

                  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
                  

                  【讨论】:

                  • 唯一通过列表测试的人:)
                  【解决方案18】:

                  如果您想使用另一个库作为解决方案,这是最好的方法

                  https://github.com/maztohir/dict-path

                  from dict-path import DictPath
                  
                  data_dict = {
                    "foo1": "bar1",
                    "foo2": "bar2",
                    "foo3": {
                       "foo4": "bar4",
                       "foo5": {
                          "foo6": "bar6",
                          "foo7": "bar7",
                       },
                    }
                  }
                  
                  data_dict_path = DictPath(data_dict)
                  data_dict_path.get('key1/key2/key3')
                  

                  【讨论】:

                    【解决方案19】:

                    我发现在我自己的代码中有用的 unutbu 答案的改编:

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

                    如果它还没有 key1,它会为 key1 生成一个字典条目,这样你就可以避免 KeyError。如果您想像我一样最终得到一个包含该密钥对的嵌套字典,这似乎是最简单的解决方案。

                    【讨论】:

                      【解决方案20】:

                      由于如果缺少一个键则引发键错误是合理的做法,我们甚至可以不检查它并得到它:

                      def get_dict(d, kl):
                        cur = d[kl[0]]
                        return get_dict(cur, kl[1:]) if len(kl) > 1 else cur
                      

                      【讨论】:

                        【解决方案21】:

                        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)
                        

                        【讨论】:

                          【解决方案22】:

                          我使用的解决方案类似于双重获取,但具有使用 if else 逻辑避免 TypeError 的额外能力:

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

                          但是,字典嵌套越多,就越麻烦。

                          【讨论】:

                            【解决方案23】:

                            对于嵌套字典/JSON 查找,您可以使用 dictor

                            pip 安装字典

                            字典对象

                            {
                                "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

                            【讨论】:

                              【解决方案24】:

                              this 答案我几乎没有改变。我添加了检查我们是否使用带有数字的列表。 所以现在我们可以以任何方式使用它。 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)
                              

                              【讨论】:

                              • 失败了:test_dict = {"a":{"b":[{"c":"value"}]}} self.assertEqual(safeget(test_dict, ["a", "b", 1, "c"], 无)
                              【解决方案25】:

                              递归方法(мб пригодится)

                              示例字典:

                              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']
                              

                              【讨论】:

                                【解决方案26】:
                                def safeget(_dct, *_keys):
                                    if not isinstance(_dct, dict): raise TypeError("Is not instance of dict")
                                    def foo(dct, *keys):
                                        if len(keys) == 0: return dct
                                        elif not isinstance(_dct, dict): return None
                                        else: return foo(dct.get(keys[0], None), *keys[1:])
                                    return foo(_dct, *_keys)
                                
                                assert safeget(dict()) == dict()
                                assert safeget(dict(), "test") == None
                                assert safeget(dict([["a", 1],["b", 2]]),"a", "d") == None
                                assert safeget(dict([["a", 1],["b", 2]]),"a") == 1
                                assert safeget({"a":{"b":{"c": 2}},"d":1}, "a", "b")["c"] == 2
                                

                                【讨论】:

                                  【解决方案27】:

                                  我已经写了一个包 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
                                  

                                  【讨论】:

                                    【解决方案28】:

                                    我的实现下降到子字典,忽略 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
                                    

                                    【讨论】:

                                      猜你喜欢
                                      • 1970-01-01
                                      • 1970-01-01
                                      • 1970-01-01
                                      • 2019-05-27
                                      • 2021-02-05
                                      • 2019-03-05
                                      • 1970-01-01
                                      相关资源
                                      最近更新 更多