检查嵌套字典中的密钥是否存在

时间:2015-06-05 23:36:35

标签: python json

我正在使用API​​调用,因此使用Python字典。

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

假设我有

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

有时键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']来检查是否存在,因此我需要添加一个对我的每个if语句尝试/ catch,好像我没有错,如果异常是catch,则try block不再执行。

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

有什么想法吗?

背后的用法是,省略无用的情况,并假设有时信息在测试['a'] ['b'] ['c'] ['d'],有时在测试['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

如果我在那里试一试,那么第一个异常不会停止执行并且不让我执行elif吗?

此外,我真的很喜欢调用test['a']['b']['c']['d']比给出密钥列表更好的方式。事实上,我希望它对我和那些阅读/使用我的代码的人们尽可能透明。

6 个答案:

答案 0 :(得分:2)

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

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等相同的顺序测试关键组合。

答案 1 :(得分:1)

这不完全是你想要的,因为它没有检查是否存在,但是这里有一个类似于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)

不幸的是,它效率不高,因为只要缺少其中一个键,它就不会停止。

答案 2 :(得分:1)

如果您正在使用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 dict,它受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'}

dict的每个级别都需要Mdict vs普通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

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

答案 3 :(得分:0)

我会创建一个递归函数。

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:])

答案 4 :(得分:0)

您可以嵌套try块来处理两种丢失键的异常。依赖于try块符合EAFP(更容易请求宽恕而不是许可)Python的哲学,而不是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循环中,以便它可以return或{{1} }。如果没有,extract a method

continue

答案 5 :(得分:0)

另一个是为了完整性,但只递归检查一个键:

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 中的键存在。