【问题标题】:Check key existence in nested dictionaries检查嵌套字典中的键是否存在
【发布时间】:2015-06-05 23:36:34
【问题描述】:

我正在处理 API 调用和 Python 字典。

但是,对于相同的请求,我并不总是键入相同的键,我想知道何时可以调用没有异常的键...

假设我有

test = {'a':{'b':{'c':{'d':'e'}}}}

有时 key d 会存在,有时则不会。有时 c 甚至都不存在。

我想以某种方式在一行中检查test['a']['b']['c']['d'] 是否存在。

到目前为止我已经尝试过:

  • 使用test.get('a', {}).get('b', {}).get('c', {}).get('d', {})。工作正常,但很乱,有时我有 5-6 个嵌套字典,名字很长......

  • 使用 try/except 块很好,但通常如果 test['a']['b']['c']['d'] 不存在,我会尝试调用 test['a']['b']['e']['f'] 来检查是否存在,因此我需要添加一个 try/catch我的每一条if语句,好像我没有错,如果catch了异常,try块就不再执行了。

我可能正试图寻找一种自反的方式来做到这一点,以我的“对象”的名称作为字符串调用一个函数,该函数将检查每个键是否存在,如果存在,则返回该对象自己。

有什么想法吗?

它背后的用法是,省略无用的情况,并假设有时信息在 test['a']['b']['c']['d'] 中,有时在 test['a' ]['b']['f']:

if test['a']['b']['c']['d'] **exists**:
    do sthg with the value of test['a']['b']['c']['d']
elif test['a']['b']['f'] **exists**:
    do sthg else with the value of test['a']['b']['f']
else:
    do sthg different

如果我在那里放了一个 try/except,第一个异常不会停止执行并且不让我执行 elif 吗?

此外,我真的很喜欢调用test['a']['b']['c']['d'] 的方式,而不是提供一个键列表。事实上,我希望它对我和阅读/使用我的代码的人来说尽可能透明。

【问题讨论】:

  • 您的代码需要做什么?看来 try/except 块是正确的方法。你想要吗?失败的深度?
  • @remram 如果密钥 d 不存在,我的信息可能会在另一个密钥中。 if test['a']['b']['c']['d'] **exists**: do sthg elif test['a']['b']['e'] **exists**: do sthg else else: do sthg else 如果我没记错的话,如果 test['a']['b']['c']['d'] 不存在并且一切都在 try/catch 中,那么 elif 甚至不会被处决吧?
  • @cristian-ciupitu 不是真的。另一方面,假设密钥存在,OP 希望通过密钥列表访问值。老实说,我什至更喜欢调用 test['a']['b']['c'] 的正常方式,而不是提供所有解决方案目前都在使用的键列表:/
  • 其实我很喜欢你原来的.get(key,{}) 方法——而且比答案更好。

标签: python json


【解决方案1】:

你可以写一个递归函数来检查:

def f(d, keys):
    if not keys:
        return True
    return keys[0] in d and f(d[keys[0]], keys[1:])

如果函数返回True,则键存在:

In [10]: f(test,"abcd")
Out[10]: True

In [11]: f(test,"abce")
Out[11]: False

如果你想测试多个组合键:

for keys in ("abce","abcr","abcd"):
    if f(test,keys):
        print(keys)
        break
abcd

返回值很简单:

def f(d, keys):
    if len(keys) == 1:
         return d[keys[0]] if keys[0] in d else False
    return keys[0] in d and f(d[keys[0]], keys[1:])

print(f(test,"abcd"))
e

您可以再次测试多个组合键:

def test_keys(keys):
    for keys in keys:
        val = f(test,keys)
        if val:
            return val
    return False


print(test_keys(("abce","abcr","abc")))

你也可以迭代地编写函数:

def f(d, keys):
    obj = object
    for k in keys:
        d = d.get(k, obj)
        if d is obj:
            return False
    return d

print(f(test,"abcd"))
e

如果要根据返回值运行条件:

def f(d, keys):
    obj = object
    for k in keys:
        d = d.get(k, obj)
        if d is obj:
            return False
    return d

from operator import mul

my_actions = {"c": mul(2, 2), "d": lambda: mul(3, 3), "e": lambda: mul(3, 3)}

for st in ("abce", "abcd", "abcf"):
    val = f(test, st)
    if val:
        print(my_actions[val]())
9

只需按照与 if/elif 等相同的顺序测试组合键。

【讨论】:

    【解决方案2】:

    这不是您想要的,因为它不检查存在,但这里有一个类似于 dict.get 方法的单行:

    In [1]: test = {'a':{'b':{'c':{'d':'e'}}}}
    In [2]: keys = 'abcd' # or ['a', 'b', 'c', 'd']
    
    In [3]: reduce(lambda d, k: d.get(k) if d else None, keys, test)
    Out[3]: 'e'
    
    In [4]: keys = 'abcf'
    
    In [5]: reduce(lambda d, k: d.get(k) if d else None, keys, test)
    

    不幸的是,它不是很有效,因为它不会在缺少一个键时立即停止。

    【讨论】:

    • 问题不是在缺少密钥时停止 - 我只是不想在缺少密钥时获得异常以便能够评估下一个可能的密钥。
    • @user2497262,正如我的演示所示,没有引发异常。
    【解决方案3】:

    如果您正在使用 JSON,您可以编写一个简单的类,以便在导入时与 dict 一起使用。

    鉴于以下 JSON:

    >>> js='{"a": {"b": {"c": {"d": "e"}}}}'
    

    如果它由对象对组成,通常会被解码为 Python dict:

    >>> import json
    >>> json.loads(js)
    {u'a': {u'b': {u'c': {u'd': u'e'}}}}
    

    作为一个普通的 Python 字典,它受制于 KeyError 缺少键。您可以使用__missing__ 钩子覆盖KeyErrors 并实现您的原始结构:

    class Mdict(dict):
        def __missing__(self, key):
            return False
    

    现在测试一下:

    >>> md=Mdict({'a':Mdict({'b':Mdict({'c':Mdict({'d':'e'})})})})
    >>> if md['a']['b']['d']:
    ...    print md['a']['b']['d']
    ... elif md['a']['b']['c']:
    ...    print 'elif', md['a']['b']['c']  
    ... 
    elif {'d': 'e'}
    

    字典的每个级别都需要是 Mdict 而不是普通的 Python dict。但是,如果您使用的是 JSON,这真的很容易实现。只需在解码 JSON 时应用 object_pairs_hook

    >>> js
    '{"a": {"b": {"c": {"d": "e"}}}}'
    >>> md=json.loads(js, object_pairs_hook=Mdict)
    

    当 JSON 被解码时,应用类 Mdict 而不是默认的 Python dict

    >>> md
    {u'a': {u'b': {u'c': {u'd': u'e'}}}}
    >>> md['a']
    {u'b': {u'c': {u'd': u'e'}}}
    >>> md['a']['c']
    False
    

    此处示例的其余部分保持不变。

    【讨论】:

    • 我喜欢这种想法。但是,这意味着我必须将我的 JSON 输出转换为我自己的字典,对吗?
    • 您可以将object_pairs_hook 与 JSON 编码器一起使用以将其应用为导入的。
    • 很好,这正是我想要的!让我按照我习惯的方式调用字典,不需要对函数进行其他调用来检查。完美:D
    • 请记住,False 在 JSON 字典中也是一个合法值。您可能希望 __missing__ 返回一个否则无法解码的值,例如 object() 以避免歧义。
    【解决方案4】:

    我会创建一个递归函数。

    def get_key(d, *args):
        if not args:
            return None
        val = d.get(args[0], None)
    
        if len(args) == 1:
            return val
        if isinstance(val, dict):
            return get_key(val, *args[1:])
    

    【讨论】:

      【解决方案5】:

      您可以嵌套try 块来处理两种丢失键的异常。依赖try 块更符合 Python 的EAFP(请求宽恕比请求许可)哲学,而不是使用前测试存在的 LBYL(先看一下)模式。在多线程程序中,它还有助于避免由于另一个线程在测试存在和使用值之间修改test 字典而导致的意外TOCTTOU(检查时间到使用时间)行为。

      try:
          value_abcd = test['a']['b']['c']['d']
      except KeyError:
          try:
              value_abf = test['a']['b']['f']
          except KeyError:
              print("do something different")
          else:
              print("value_abf is", value_abf)
      else:
          print("value_abcd is", value_abcd)
      

      从那以后,我注意到您拥有的钥匙远不止两个。用这么多键嵌套try 块将创建一个arrowhead anti-pattern。因此,您可以尝试以下构造来处理相同缩进级别的所有键,只要访问发生在函数或for 循环中,以便它可以returncontinue。如果没有,extract a method

      try:
          value_abcd = test['a']['b']['c']['d']
      except KeyError:
          pass
      else:
          print("value_abcd is", value_abcd)
          return  # or continue if doing this in a loop
      
      try:
          value_abf = test['a']['b']['f']
      except KeyError:
          pass
      else:
          print("value_abf is", value_abf)
          return
      
      print("do something different")
      

      【讨论】:

      • 如果 OP 有 20 个条件,有 20 个嵌套的 try/excepts 会发生什么?
      • @user2497262 我在另一个解决方案中进行了编辑,该解决方案可以处理多个键而无需制作箭头。
      【解决方案6】:

      另一个用于完整性,但仅递归检查一个键:

      def hasKey(d, key):
          found = False
          if isinstance(d, dict):
              for k in d:
                  found = True if k == key else found or hasKey(d[k], key)
          if isinstance(d, list):
              for i in d:
                  found = found or hasKey(i, key)
          return found
      

      检查key 是否作为嵌套字典map 中的键存在。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-02-28
        • 1970-01-01
        • 1970-01-01
        • 2015-06-02
        • 1970-01-01
        • 1970-01-01
        • 2022-01-22
        • 1970-01-01
        相关资源
        最近更新 更多