Python检查多层字典键是否存在

16

许多SO贴展示了如何高效地检查字典中键的存在性,例如,Check if a given key already exists in a dictionary

那么,如何对于多级键进行此操作呢?例如,如果d["a"]["b"]是一个字典,如何检查d["a"]["b"]["c"]["d"]是否存在,而不像这样做一些可怕的事情:

if "a" in d and isInstance(d["a"], dict) and "b" in d["a"] and isInstance(d["a"]["b"], dict) and ...

是否有类似于

这样的语法

if "a"/"b"/"c"/"d" in d

我实际上使用它的目的是:我们有一些JSON数据,使用 simplejson 解析成字典,我需要从中提取值。有些值嵌套了三到四层;但有时候这个值根本不存在。所以我想要像这样的东西:

val = None if not d["a"]["b"]["c"]["d"] else  d["a"]["b"]["c"]["d"] #here d["a"]["b"] may not even exist

编辑: 如果某些子键存在但不是字典,例如 d["a"]["b"] = 5,则最好不要崩溃。


这不是语言的基本特性,因为没有办法添加新的语法。您可以定义一个新类,覆盖被“x in y”表达式调用的__contains__函数。您想要语法的效率还是执行的效率?它们可能不是同一件事。 - mobiusklein
我的目标是高效的语法,但这是在假定 O(1) 字典查找时间将被保留的情况下。然而,我意识到抛出异常是昂贵的,所以也许这比简单地检查键的存在更加复杂。 - Tommy
异常处理不会是昂贵的部分。正如utdemir所指出的那样,您基本上想要的并不在语言中。Meitham的答案是最接近您想要的,而不需要做更多的工作,就像我之前提到的定义一个类,然后费力地让simplejson将对象解包到其中,而不是普通的字典。 - mobiusklein
5个回答

25

不幸的是,目前没有内置的语法或常用库可以查询字典。

然而,我认为你可以做的最简单的事情(并且我认为它足够高效)是:

d.get("a", {}).get("b", {}).get("c")

编辑:并不常见,但有一个https://github.com/akesterson/dpath-python

编辑2:示例:

>>> d = {"a": {"b": {}}}
>>> d.get("a", {}).get("b", {}).get("c")
>>> d = {"a": {}}
>>> d.get("a", {}).get("b", {}).get("c")
>>> d = {"a": {"b": {"c": 4}}}
>>> d.get("a", {}).get("b", {}).get("c")
4

help({}.get)D.get(k[,d]) -> 如果k在D中,则返回D[k],否则返回d。d默认为None。。如果找到了该项,则返回该项,否则返回None - utdemir
1
好的,你说如果d["a"]["b"]["c"]是一个字典(dict),所以我的代码假设它们是字典。 - utdemir
3
那么它就不起作用了。如果["c"]还没有定义呢?这个问题的重点是要看一个多层字典键是否有效。它可能有多个级别的偏差;正如 isInstance 的荒谬所示。 - Tommy
如果其中一个子键可以是除了“dict”以外的类型,请澄清您的问题。如果它们是“dict”或不存在,则我的解决方案有效。 - utdemir
1
无法相信这个答案得了负分。我认为它很棒。 - lucasnadalutti
显示剩余3条评论

3
这可能不是一个好主意,我不推荐在生产环境中使用它。然而,如果你只是为了学习目的而这样做,以下内容可能适合你。
def rget(dct, keys, default=None):
    """
    >>> rget({'a': 1}, ['a'])
    1
    >>> rget({'a': {'b': 2}}, ['a', 'b'])
    2
    """
    key = keys.pop(0)
    try:
        elem = dct[key]
    except KeyError:
        return default
    except TypeError:
        # you gotta handle non dict types here
        # beware of sequences when your keys are integers
    if not keys:
        return elem
    return rget(elem, keys, default)

2

1
一个非递归版本,与@Meitham的解决方案非常相似,不会改变要查找的键值。如果源字典中存在完全相同的结构,则返回True/False
def subkey_in_dict(dct, subkey):
    """ Returns True if the given subkey is present within the structure of the source dictionary, False otherwise.
    The format of the subkey is parent_key:sub_key1:sub_sub_key2 (etc.) - description of the dict structure, where the
    character ":" is the delemiter.

    :param dct: the dictionary to be searched in.
    :param subkey: the target keys structure, which should be present.
    :returns Boolean: is the keys structure present in dct.
    :raises AttributeError: if subkey is not a string.
    """
    keys = subkey.split(':')
    work_dict = dct
    while keys:
        target = keys.pop(0)
        if isinstance(work_dict, dict):
            if target in work_dict:
                if not keys:    # this is the last element in the input, and it is in the dict
                    return True
                else:   # not the last element of subkey, change the temp var
                    work_dict = work_dict[target]
            else:
                return False
        else:
            return False

被检查的结构以parent_key:sub_key1:sub_sub_key2的形式,其中:字符是分隔符。显然-它将区分大小写,并且如果字典中有列表,则会停止(返回False)。
示例用法:
dct = {'a': {'b': {'c': {'d': 123}}}}

print(subkey_in_dict(dct, 'a:b:c:d'))    # prints True
print(subkey_in_dict(dct, 'a:b:c:d:e'))  # False
print(subkey_in_dict(dct, 'a:b:d'))      # False
print(subkey_in_dict(dct, 'a:b:c'))      # True

0

这是我通常使用的

def key_in_dict(_dict: dict, key_lookup: str, separator='.'):
    """
        Searches for a nested key in a dictionary and returns its value, or None if nothing was found.
        key_lookup must be a string where each key is deparated by a given "separator" character, which by default is a dot
    """
    keys = key_lookup.split(separator)
    subdict = _dict

    for k in keys:
        subdict = subdict[k] if k in subdict else None
        if subdict is None: break

    return subdict

如果存在则返回键,否则返回None。
key_in_dict({'test': {'test': 'found'}}, 'test.test') // 'found'
key_in_dict({'test': {'test': 'found'}}, 'test.not_a_key') // None

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接